samagotchi 0.94.0 → 0.96.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -1
- data/README.md +7 -7
- data/bin/chi +6 -2
- data/docs/architecture.md +4 -5
- data/docs/cli.md +11 -3
- data/docs/configuration.md +49 -86
- data/docs/hooks.md +1 -1
- data/docs/internals/context-telemetry.md +1 -1
- data/docs/internals/engine.md +7 -4
- data/docs/plugins.md +4 -3
- data/docs/sessions.md +8 -2
- data/docs/testing.md +6 -9
- data/lib/samagotchi/bootstrap/config_writer.rb +1 -5
- data/lib/samagotchi/bundle_command/try/sandbox.rb +8 -2
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +6 -7
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/self_map.md +1 -1
- data/lib/samagotchi/children_status.rb +1 -1
- data/lib/samagotchi/client.rb +59 -196
- data/lib/samagotchi/config.rb +74 -15
- data/lib/samagotchi/engine.rb +7 -7
- data/lib/samagotchi/host_config.rb +20 -27
- data/lib/samagotchi/host_registry.rb +8 -8
- data/lib/samagotchi/kernel_loop.rb +46 -8
- data/lib/samagotchi/model_profile.rb +2 -3
- data/lib/samagotchi/plugin/sessions.rb +4 -3
- data/lib/samagotchi/self_report.rb +0 -2
- data/lib/samagotchi/send_command.rb +1 -1
- data/lib/samagotchi/session.rb +13 -1
- data/lib/samagotchi/session_commands.rb +13 -0
- data/lib/samagotchi/session_continue.rb +8 -8
- data/lib/samagotchi/session_delete_command.rb +1 -1
- data/lib/samagotchi/session_manager.rb +13 -7
- data/lib/samagotchi/sessions_command.rb +8 -6
- data/lib/samagotchi/system_prompt.rb +7 -21
- data/lib/samagotchi/terminal_ui/thinking_line.rb +2 -4
- data/lib/samagotchi/thinking.rb +35 -2
- data/lib/samagotchi/thinking_notices.rb +11 -4
- data/lib/samagotchi/title_command.rb +61 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/vision_support.rb +0 -3
- data/lib/samagotchi/web/app.rb +4 -4
- data/lib/samagotchi/web/public/app.js +12 -0
- data/lib/samagotchi/web/public/chains.js +21 -8
- data/lib/samagotchi/web/public/format.js +4 -3
- data/lib/samagotchi/web/public/index.html +7 -0
- data/lib/samagotchi/web/public/info_bar_model.js +2 -0
- data/lib/samagotchi/web/public/session_rename.js +86 -0
- data/lib/samagotchi/web/public/session_state.js +4 -2
- data/lib/samagotchi/web/session_summary.rb +3 -0
- data/lib/samagotchi/worker.rb +5 -2
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 919ca9857f9debe0c23587e3061cc7d7723966067a236054d06acc68bb811b35
|
|
4
|
+
data.tar.gz: 3b5588e7c7de075d1dc7a7d84d57a39222ddd63342931aef4764a5422cc1959b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2928c6af604c2e9e33728949ae701d814ca61c510bec264004c56b00986c46ec6b2a23dfc449e832eed18246b33c57bc04b3d422e02dc3fe21667fd890b29fce
|
|
7
|
+
data.tar.gz: 4171544e495614b7f7ff23ef92b484d67dc9aa34c3624b2ac7459d8bf1e9fd93b36fc7ca329ca11e1dead80da44a032146c617900484b934fc11f2d3c4fbf159
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,54 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.96.0] - 2026-10-21
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Session titles: `/title Coordinator` names a session (`/title` shows it, `/title --clear` drops it), from any UI or
|
|
16
|
+
`chi send`, mid-turn too. Every list, card, chain row, top bar, tab and notification shows it in place of the first
|
|
17
|
+
message, `chi sessions list` too (json has `title`), and a continue's next link keeps it. The web's info bar has `✎`
|
|
18
|
+
to rename the open session. See [docs/sessions.md](docs/sessions.md#session-titles).
|
|
19
|
+
|
|
20
|
+
Update with `chi update` (the system bundle 0.96.0), then restart `chi web`: a `chi web` or session worker from
|
|
21
|
+
before the update drops a session's title when it saves the session.
|
|
22
|
+
|
|
23
|
+
## [0.95.0] - 2026-10-21
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
|
|
27
|
+
- chi's own `mlx` and `omlx` transports (`api:` / `transport:` on a host, `server.transport`,
|
|
28
|
+
`SAMAGOTCHI_SERVER_TRANSPORT`): mlx_lm.server and oMLX are chat hosts now, `url: http://HOST:PORT/v1, api: openai`
|
|
29
|
+
(on oMLX the chat endpoint matched or beat the native one, tool calls intact). llama.cpp is the only raw-prompt
|
|
30
|
+
server. A config that still names `mlx` or `omlx` stops chi at start with one line: `Error: config.yml
|
|
31
|
+
hosts.mac.api: omlx was removed; for mlx_lm.server or oMLX use url: http://HOST:PORT/v1, api: openai in a hosts:
|
|
32
|
+
entry`. Name the model as the server's `/v1/models` lists it (oMLX's prefixed `mlx-community--…` id); the short
|
|
33
|
+
selector oMLX's transport used to resolve now 404s. See
|
|
34
|
+
[docs/configuration.md](docs/configuration.md#model-server-api).
|
|
35
|
+
- The transport setting: `server.transport`, a host's `transport:`, `SAMAGOTCHI_SERVER_TRANSPORT` and
|
|
36
|
+
`--server-transport` (llama.cpp is the one raw-prompt server, and a host without `api:` talks it, as
|
|
37
|
+
`api: llama_cpp`). A config or environment still setting one to `llama_cpp` warns at start and runs on:
|
|
38
|
+
`Warning: config: 'server.transport' was removed: llama.cpp is chi's only raw-prompt transport (a hosts entry
|
|
39
|
+
without api: is api: llama_cpp); remove it`; `mlx` or `omlx` stops chi as above. The flag is an invalid option.
|
|
40
|
+
- The Qwen turn preamble: a native `qwen36` prompt no longer asks the model to open its thinking with a `TURN:` line,
|
|
41
|
+
and the `thinking.turn_preamble` setting goes with it (`--no-thinking-turn-preamble`,
|
|
42
|
+
`SAMAGOTCHI_THINKING_TURN_PREAMBLE`). A config.yml that still sets it gets `Warning: config: unknown key
|
|
43
|
+
'thinking.turn_preamble'` at start and runs on; the flag is an invalid option.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- Native Qwen3.6 on llama.cpp (`api: llama_cpp`, profile `qwen36`) thinks again: where the server's chat template
|
|
48
|
+
opens a thought after the assistant cue (Qwen3.5 and later, read from the model's `/props`), chi's prompt ends with
|
|
49
|
+
`<think>` and a newline as the template's does, at every level but `off`; before, the model wrote an empty thought
|
|
50
|
+
and answered at once. Its reasoning streams as thinking and stays out of the answer. Turns take longer; `thinking:
|
|
51
|
+
off` answers at once as before. An older Qwen3 or other template, or no template in `/props`, keeps the old prompt.
|
|
52
|
+
`low`, `medium` and `high` think the same there, which the notice now says. A tool call the model writes inside a
|
|
53
|
+
closed thought no longer runs. See [docs/configuration.md](docs/configuration.md#thinking).
|
|
54
|
+
- `chi bundle try` with a config chi stops at (e.g. a removed `api: omlx`) fails the install with that one line, not a
|
|
55
|
+
backtrace.
|
|
56
|
+
|
|
57
|
+
Update with `chi update` (the system bundle 0.95.0), then restart `chi web`.
|
|
58
|
+
|
|
11
59
|
## [0.94.0] - 2026-10-20
|
|
12
60
|
|
|
13
61
|
### Added
|
|
@@ -3655,7 +3703,9 @@ and long-lived sessions.
|
|
|
3655
3703
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
3656
3704
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
3657
3705
|
|
|
3658
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
3706
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.96.0...HEAD
|
|
3707
|
+
[0.96.0]: https://github.com/dm1try/samagotchi/compare/v0.95.0...v0.96.0
|
|
3708
|
+
[0.95.0]: https://github.com/dm1try/samagotchi/compare/v0.94.0...v0.95.0
|
|
3659
3709
|
[0.94.0]: https://github.com/dm1try/samagotchi/compare/v0.93.0...v0.94.0
|
|
3660
3710
|
[0.93.0]: https://github.com/dm1try/samagotchi/compare/v0.92.0...v0.93.0
|
|
3661
3711
|
[0.92.0]: https://github.com/dm1try/samagotchi/compare/v0.91.0...v0.92.0
|
data/README.md
CHANGED
|
@@ -19,11 +19,11 @@ command.
|
|
|
19
19
|
its own API (the native path, and the default)
|
|
20
20
|
|
|
21
21
|
Tested: llama.cpp native (Qwen, Gemma 4), [Splash](https://github.com/incoai/splash)
|
|
22
|
-
|
|
23
|
-
with `api_key_env`).
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
[Configuration](docs/configuration.md#model-server-
|
|
22
|
+
and [oMLX](https://github.com/jundot/omlx) over the OpenAI API, and
|
|
23
|
+
[OpenRouter](https://openrouter.ai) (remote, OpenAI API with `api_key_env`).
|
|
24
|
+
vLLM, LM Studio, Ollama, mlx-lm and other OpenAI-compatible servers should work
|
|
25
|
+
over the OpenAI API but are not tested; see
|
|
26
|
+
[Configuration](docs/configuration.md#model-server-api).
|
|
27
27
|
|
|
28
28
|
## Install
|
|
29
29
|
|
|
@@ -110,7 +110,7 @@ hosts:
|
|
|
110
110
|
api: openai
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
`chi self` shows the model and host chi will use. Other settings (multiple hosts,
|
|
113
|
+
`chi self` shows the model and host chi will use. Other settings (multiple hosts, model server APIs, timeouts, the idle recap) are in
|
|
114
114
|
[Configuration](docs/configuration.md).
|
|
115
115
|
|
|
116
116
|
## Use
|
|
@@ -207,7 +207,7 @@ once, for the session, for the repo, or for the whole rule in the repo.
|
|
|
207
207
|
- [Releasing](docs/releasing.md): versions, the changelog, how a release is published
|
|
208
208
|
|
|
209
209
|
- [CLI and REPL](docs/cli.md): flags, sharing a session, web UI, `/model`, status line
|
|
210
|
-
- [Configuration](docs/configuration.md): `config.yml`, hosts, model server
|
|
210
|
+
- [Configuration](docs/configuration.md): `config.yml`, hosts, model server APIs, timeouts, retries, logs
|
|
211
211
|
- [Memory](docs/memory.md): scopes, model-specific overlays and model notes
|
|
212
212
|
- [Sessions](docs/sessions.md): storage, retention, `chi sessions`
|
|
213
213
|
- [Attached context](docs/context.md): `chi context`, sources, scopes, recipes (a file, a note, a stream), waking, the github-pr bundle, safety
|
data/bin/chi
CHANGED
|
@@ -11,7 +11,12 @@ Samagotchi::Utf8Default.apply!
|
|
|
11
11
|
require "optparse"
|
|
12
12
|
require "samagotchi/config"
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
begin
|
|
15
|
+
Samagotchi::ConfigFile.load!
|
|
16
|
+
rescue Samagotchi::ConfigFile::RemovedApi => e
|
|
17
|
+
warn "Error: #{e.message}"
|
|
18
|
+
exit 1
|
|
19
|
+
end
|
|
15
20
|
require "samagotchi"
|
|
16
21
|
|
|
17
22
|
# `chi self`: print version, source dir, config/memory/session paths, model and
|
|
@@ -173,7 +178,6 @@ parser = OptionParser.new do |opts|
|
|
|
173
178
|
desc += " (#{entry.enum_values.join("|")})" if entry.type == :enum
|
|
174
179
|
desc += " (#{value_hints[entry.key]})" if value_hints.key?(entry.key)
|
|
175
180
|
desc += "; only without a hosts: config" if %w[server.host server.port].include?(entry.key)
|
|
176
|
-
desc += "; hosts: entries without a transport use it" if entry.key == "server.transport"
|
|
177
181
|
if entry.type == :enum
|
|
178
182
|
opts.on("#{entry.cli_flag} VALUE", entry.enum_values, desc) { |v| cli_overrides[entry.key] = v }
|
|
179
183
|
else
|
data/docs/architecture.md
CHANGED
|
@@ -489,7 +489,7 @@ on every read, never kept, since config.yml and a model listing can move it with
|
|
|
489
489
|
|
|
490
490
|
| Loop | Class | Host | Talks through |
|
|
491
491
|
|---|---|---|---|
|
|
492
|
-
| Raw prompt | `KernelLoop`, wrapped by `LLM::NativeBackend` | no `api:`, or `llama_cpp
|
|
492
|
+
| Raw prompt | `KernelLoop`, wrapped by `LLM::NativeBackend` | no `api:`, or `llama_cpp` | `Client` (llama.cpp `/completion`), chi's own Gemma/Qwen prompt and tool-call parsing |
|
|
493
493
|
| Chat | `LLM::ChatLoop` | `api: openai` | `LLM::OpenAIChat` (`/v1/chat/completions`, streamed, native tool calls) |
|
|
494
494
|
| Chat | `LLM::ChatLoop` | `api: anthropic` | `LLM::AnthropicMessages` (`/v1/messages`, streamed SSE, native `tool_use`) |
|
|
495
495
|
|
|
@@ -503,7 +503,7 @@ hooks and tools whichever model made it. Both loops' `generation_chunk` carries
|
|
|
503
503
|
clients: `text`, `thinking`, `tool_call` and chi's `counts`, no `payload` or `content`; see the
|
|
504
504
|
Bridge's stream above): the chat loop's thinking is the
|
|
505
505
|
server's `reasoning_content`; the raw-prompt loop splits each generation's
|
|
506
|
-
stream with a fresh `ThoughtStreamSplitter
|
|
506
|
+
stream with a fresh `ThoughtStreamSplitter`, seeded with the prompt's prefill (Qwen's `<think>` and Gemma's
|
|
507
507
|
`<|channel>thought … <channel|>` are thinking, tool-call bodies are dropped
|
|
508
508
|
from `text:`). The chat loop's model turns keep their `tool_calls` and tool results their `tool_call_id`,
|
|
509
509
|
so later requests and resumed sessions pair them, and their reasoning (`thinking:`), which goes back as
|
|
@@ -521,10 +521,9 @@ as an empty answer and asks again.
|
|
|
521
521
|
Both loops keep one `EmptyAnswerRetry` per turn (the `retry.empty_answer`
|
|
522
522
|
budget, the hidden nudge, the retry's sampling), and both end each
|
|
523
523
|
generation's `:generation_completed` with a `finish_reason`: the chat host's
|
|
524
|
-
own, or for the raw-prompt loop what `Client
|
|
524
|
+
own, or for the raw-prompt loop what `Client.finish_reason_from`
|
|
525
525
|
reads from the stream's last payload (llama.cpp `/completion`'s `stop_type`:
|
|
526
|
-
`limit` is `length`, `eos` and a stop word are `stop
|
|
527
|
-
its own). An empty answer with a `length` stop and the context 90 % full or
|
|
526
|
+
`limit` is `length`, `eos` and a stop word are `stop`). An empty answer with a `length` stop and the context 90 % full or
|
|
528
527
|
more is not asked again (a full window, not a thinking loop); with no finish
|
|
529
528
|
reason it is. Both also keep one `ContextStatus` per turn: before each request
|
|
530
529
|
it estimates how full the window is (the server's last count plus what the turn
|
data/docs/cli.md
CHANGED
|
@@ -653,6 +653,14 @@ attach and `chi -p --non-interactive` are refused. `/parent-answers` alone answe
|
|
|
653
653
|
mid-turn too; a change waits for the turn's end. No chip: its reply is where the
|
|
654
654
|
web shows it. See [Guardrails](guardrails.md#answers-from-a-parent-agent).
|
|
655
655
|
|
|
656
|
+
`/title` shows the session's title (`title: Coordinator`, or `title: none (shows
|
|
657
|
+
the first message: …)`); `/title TEXT` names the session (whitespace collapsed,
|
|
658
|
+
cut at 80 characters), `/title --clear` drops the name (anything after `--clear`
|
|
659
|
+
is refused with the usage line). It is saved at once,
|
|
660
|
+
mid-turn too, and the lists, cards, top bar and tab show it in place of the first
|
|
661
|
+
message; a continue's next link keeps it. The web's info bar has `✎` for it. See
|
|
662
|
+
[Session titles](sessions.md#session-titles).
|
|
663
|
+
|
|
656
664
|
A `/thinking`, `/reasoning-history` or `/toolkit` change that restarts the prompt cache
|
|
657
665
|
asks first: the reply says what it costs, changes nothing, and names the line
|
|
658
666
|
that applies it (`/thinking low --yes`). `--yes` (or `-y`) applies it at once.
|
|
@@ -812,7 +820,7 @@ like `[image shot.png 1280×800 not sent: an older image (chi sends up to the
|
|
|
812
820
|
newest 20)]`.
|
|
813
821
|
|
|
814
822
|
A model that can't see images (a text-only model, llama.cpp without
|
|
815
|
-
`--mmproj
|
|
823
|
+
`--mmproj`) refuses a turn with images before sending it:
|
|
816
824
|
`host main can't take images: …; send text only, or pick a model that can see
|
|
817
825
|
images (/model)`, and the typed text (and the web's chips) come back. When chi
|
|
818
826
|
can't tell beforehand, the provider's refusal gives the same line. Images already
|
|
@@ -1175,8 +1183,8 @@ While a turn runs, one row above the prompt says what it is doing, the spinner f
|
|
|
1175
1183
|
- `| thinking…` while the model starts, then `| thinking · <sentence>`: the newest complete
|
|
1176
1184
|
sentence of its thinking, or of its answer (`writing ·`), like the web's thinking ticker: the
|
|
1177
1185
|
same sentence rules (a list number such as `118.` is no sentence end; a newline is one), and the
|
|
1178
|
-
row changes at most once every 1.5 s so it doesn't flicker. A long sentence is cut with
|
|
1179
|
-
|
|
1186
|
+
row changes at most once every 1.5 s so it doesn't flicker. A long sentence is cut with `…`, and
|
|
1187
|
+
inline markdown is left out.
|
|
1180
1188
|
- `| waiting for the first token… 5s` after 2 s with nothing streamed.
|
|
1181
1189
|
- `| running execute…` while a tool runs (its `tool>` line prints when it ends).
|
|
1182
1190
|
- `| ↻ retrying (ECONNREFUSED) in 0.5s, 1/3` while a failed request is asked again: why (the HTTP status such as
|
data/docs/configuration.md
CHANGED
|
@@ -32,13 +32,12 @@ thinking:
|
|
|
32
32
|
|
|
33
33
|
# Multi-host (optional): aggregated /models and per-model routing.
|
|
34
34
|
# A bare default.model uses the default host; host:model pins to a host.
|
|
35
|
-
#
|
|
36
|
-
#
|
|
35
|
+
# A host talks llama.cpp's own API (api: llama_cpp, the default); api: openai
|
|
36
|
+
# makes it use the OpenAI chat API instead of chi's raw prompt.
|
|
37
37
|
hosts:
|
|
38
38
|
main:
|
|
39
39
|
host: localhost
|
|
40
40
|
port: 8080
|
|
41
|
-
transport: llama_cpp
|
|
42
41
|
small-box:
|
|
43
42
|
host: 192.0.2.20
|
|
44
43
|
port: 8080
|
|
@@ -142,7 +141,7 @@ or `chi web` restart.
|
|
|
142
141
|
| `default.model` | New session | Existing sessions keep their saved model; change one with `/model`. |
|
|
143
142
|
| `default.input`, `no_default_input`, `no_interrupt` | Next CLI run | These set up that run's first prompt or turn limit. |
|
|
144
143
|
| `default.max_tokens`, `max_tool_output_chars`, `read.*`, `execute.*` except `execute.description`, `image.*` | Next matching request or tool use | An operation already in progress keeps its resolved limits. |
|
|
145
|
-
| `server.host`, `server.port`, `
|
|
144
|
+
| `server.host`, `server.port`, `hosts.*` | Worker start | Includes host addresses, APIs, declared models and host-level settings. The web model picker refreshes `hosts:` on file change; a running worker does not. |
|
|
146
145
|
| `server.open_timeout`, `server.read_timeout`, `server.first_token_timeout` | Worker start | The worker's clients retain the timeouts set when they were built. |
|
|
147
146
|
| `model_aliases` | Next model lookup | A session keeps its resolved model unless it is explicitly switched. |
|
|
148
147
|
| `models.<key>.profile` | Session start or `/model` | It is resolved when that model is selected; editing it does not switch the current model's profile by itself. |
|
|
@@ -156,7 +155,7 @@ or `chi web` restart.
|
|
|
156
155
|
| `append_system_prompt` | Worker start for the file list | The config list is not saved in the session file. File text is read when the prompt is built; `/model` rebuilds it, while an already-built prompt is cached. CLI `--append-system-prompt` files are saved in the session. |
|
|
157
156
|
| `toolkit.default`, `toolkit.child` | Session creation | The chosen toolkit is saved; changing the defaults does not move existing sessions. |
|
|
158
157
|
| `toolkit.kits`, `execute.description`, `llm_context.policy` | Worker start | These are held by the engine's toolkit or tool registry. |
|
|
159
|
-
| `
|
|
158
|
+
| `skip_agents_md` | Prompt rebuild | A running engine caches its built prompt; a new worker or `/model` rebuild uses the changed value. |
|
|
160
159
|
| `context.*` except `context.every_seconds`, `llm_context.*` except `llm_context.policy`, `steer.cut_after`, `memory.index_warn_tokens`, `status.line` | Next matching turn, display, poll or write | Readers resolve these settings when they perform that operation. Session-specific LLM context and thinking values remain saved in the session. |
|
|
161
160
|
| `context.every_seconds` | Next context poll | The poller checks it for each source that has no explicit interval. |
|
|
162
161
|
| `guardrails.*` | Next tool call or approval | Guardrail rules and disable lists are reloaded when the config file changes; rule settings take effect on that evaluation. `guardrails.parent_answers` is checked when a parent answers a question. |
|
|
@@ -188,64 +187,40 @@ An environment name used as a top-level key (`SAMAGOTCHI_DEFAULT_MODEL: my-model
|
|
|
188
187
|
the old flat form) is not read: it warns as an unknown key with the nested one
|
|
189
188
|
to use (`did you mean 'default.model'?`).
|
|
190
189
|
|
|
191
|
-
## Model Server
|
|
190
|
+
## Model Server API
|
|
192
191
|
|
|
193
|
-
Chi talks to a model server over HTTP
|
|
192
|
+
Chi talks to a model server over HTTP in one of two ways, set by the host's `api:`:
|
|
194
193
|
|
|
195
|
-
- `llama_cpp` (default): llama.cpp's native `/completion
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
- `
|
|
199
|
-
continuous batching + tiered SSD KV cache) using the same `/v1/completions`
|
|
200
|
-
and `/v1/models` endpoints as `mlx`.
|
|
194
|
+
- `llama_cpp` (the default): chi's own raw-prompt loop on llama.cpp's native `/completion`,
|
|
195
|
+
`/models` and `/props` endpoints;
|
|
196
|
+
- `openai`: the OpenAI chat API at `http://HOST:PORT/v1`, or at `url:` (see below);
|
|
197
|
+
- `anthropic`: the Messages API, see [Anthropic Messages hosts](#anthropic-messages-hosts).
|
|
201
198
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
only the request/response shape differs. oMLX's default server port is `8000` (not
|
|
208
|
-
`8080`), so point `server.port` at it. With `hosts:` each entry may set `transport: llama_cpp|mlx|omlx` to override the
|
|
209
|
-
global transport per host (`lib/samagotchi/host_registry.rb`).
|
|
210
|
-
|
|
211
|
-
Each host may also set `api:`, which says how chi talks to it:
|
|
212
|
-
|
|
213
|
-
- `llama_cpp`, `mlx` or `omlx`: chi's own raw-prompt loop (the value is also the
|
|
214
|
-
host's transport, so don't set a different `transport:` next to it);
|
|
215
|
-
- `openai`: the OpenAI chat API at `http://HOST:PORT/v1`, or at `url:` (see below).
|
|
216
|
-
|
|
217
|
-
Without `api:` a host uses the raw-prompt loop, as before. The loop follows the
|
|
199
|
+
Without `api:` a host (and the `server.host`/`server.port` one when there is no `hosts:` map)
|
|
200
|
+
uses the raw-prompt loop. There is no transport setting: `server.transport`, a host's
|
|
201
|
+
`transport:` and `SAMAGOTCHI_SERVER_TRANSPORT` were removed with the `mlx` and `omlx`
|
|
202
|
+
transports. Still set to `llama_cpp`, each warns at start and changes nothing (remove it);
|
|
203
|
+
set to `mlx` or `omlx`, chi stops with the line below. The loop follows the
|
|
218
204
|
model's host, so `/model other-host:model` can move a session between the two.
|
|
219
205
|
Workers started by plain `chi`, `chi web` or `--attach` get the same hosts, `api:` included.
|
|
220
206
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
host: 127.0.0.1
|
|
227
|
-
port: 8080
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
```shell
|
|
231
|
-
mlx_lm.server --model mlx-community/Qwen3-14B-Instruct-4bit
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
Example for oMLX:
|
|
207
|
+
**mlx-lm and oMLX** ([mlx-lm](https://github.com/ml-explore/mlx-lm)'s `mlx_lm.server`,
|
|
208
|
+
[oMLX](https://github.com/jundot/omlx)) are chat hosts: give them `api: openai` with their `/v1` url.
|
|
209
|
+
chi's own `mlx` and `omlx` transports were removed (their chat endpoint matched or beat them); a config
|
|
210
|
+
still naming `api:`/`transport:` `mlx` or `omlx` stops chi at start with one line saying what to write.
|
|
211
|
+
oMLX's default port is `8000`; an oMLX started with an API key needs `api_key_env:`:
|
|
235
212
|
|
|
236
213
|
```yaml
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
214
|
+
hosts:
|
|
215
|
+
omlx:
|
|
216
|
+
url: http://192.0.2.10:8000/v1
|
|
217
|
+
api: openai
|
|
218
|
+
api_key_env: OMLX_API_KEY
|
|
241
219
|
```
|
|
242
220
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
own chat template on this endpoint. Only the Gemma4 (`<|tool_call>…`) and Qwen3.6
|
|
247
|
-
(`[[…]]`/`<|tool_call>`) tool-call formats are in scope; GLM/Mistral/Kimi/MiniMax
|
|
248
|
-
formats are not parsed.
|
|
221
|
+
Name the model exactly as the server's `/v1/models` lists it (oMLX: e.g. `mlx-community--Qwen3.6-35B-A3B-4bit`;
|
|
222
|
+
a short name 404s): `chi models` and the `/model` picker show them. Tool calls come back as the chat API's own
|
|
223
|
+
`tool_calls`, and the model's reasoning as thinking.
|
|
249
224
|
|
|
250
225
|
For an OpenAI Chat Completions server such as [Splash](https://github.com/incoai/splash)
|
|
251
226
|
(a fast inference engine for Macs that works well for a local setup alongside
|
|
@@ -339,8 +314,7 @@ expose the OpenAI-compatible chat endpoint. In verbose mode (`-v`), chi prints t
|
|
|
339
314
|
starting model uses (`[verbose] backend=chat` or `backend=native`); request
|
|
340
315
|
bodies are not logged. The interactive REPL, `--prompt`,
|
|
341
316
|
workers, and resumed sessions all use the loop of the model's host. Hosts without
|
|
342
|
-
`api
|
|
343
|
-
transport path.
|
|
317
|
+
a chat `api:` keep using llama.cpp's native `/completion`.
|
|
344
318
|
|
|
345
319
|
### API keys from the Keychain
|
|
346
320
|
|
|
@@ -470,32 +444,14 @@ The same test works against a llama.cpp OpenAI-compatible server by changing
|
|
|
470
444
|
the host, port, and model values. The test is skipped unless
|
|
471
445
|
`SAMAGOTCHI_INTEGRATION=1` is set; see [Testing](testing.md).
|
|
472
446
|
|
|
473
|
-
oMLX's known tool-call limitation (a stream filter that strips markup) only
|
|
474
|
-
affects its `/v1/chat/completions` endpoint, not the `/v1/completions` endpoint
|
|
475
|
-
chi uses, so raw `[[…]]`/`<|tool_call>` markers stream through untouched.
|
|
476
|
-
|
|
477
447
|
`default.model` (config default) and `/model` (runtime effective) pick the model; the status line and `/model`
|
|
478
448
|
output always render the runtime effective model (showing default when diverged). Which prompt format it gets is the
|
|
479
|
-
prompt profile (see "Prompt profile" below).
|
|
480
|
-
|
|
481
|
-
- **mlx** (`mlx_lm.server`): the `model` field is omitted entirely — the server
|
|
482
|
-
uses whatever was loaded via its own `--model` CLI flag.
|
|
483
|
-
- **omlx**: the server *requires* a `model` field and returns `HTTP 400`
|
|
484
|
-
(`model: Field required`) without it, so samagotchi forwards the selector
|
|
485
|
-
resolved to the exact id listed in the server's `/v1/models` — matched by exact
|
|
486
|
-
(case-insensitive) first, then substring, then passed through unchanged. That
|
|
487
|
-
resolved id is usually prefixed (e.g. `mlx-community--gemma-4-e4b-it-4bit`), so a
|
|
488
|
-
short selector such as `gemma-4-e4b-it-4bit` is what you set in
|
|
489
|
-
`default.model`. An unknown selector passes through raw and oMLX 404s,
|
|
490
|
-
listing its available models; if `/v1/models` is unreachable, samagotchi falls
|
|
491
|
-
back to the raw selector and lets the server decide (its own 400/404). Either
|
|
492
|
-
error fails the turn with the server's message (see "Server errors" below). Runtime
|
|
493
|
-
model switch re-resolves each completion (the `/v1/models` id list is cached per
|
|
494
|
-
client; the selector itself is re-resolved every time).
|
|
449
|
+
prompt profile (see "Prompt profile" below). The request's `model` field is the selector as written (host prefix
|
|
450
|
+
dropped).
|
|
495
451
|
|
|
496
452
|
## Prompt profile
|
|
497
453
|
|
|
498
|
-
A native host (`llama_cpp
|
|
454
|
+
A native host (`llama_cpp`) gets a raw prompt in one model family's format: its turn markers, tool-call
|
|
499
455
|
syntax, thought tags and stop sequences. That is the prompt profile, `qwen36` or `gemma4`. A wrong one is not just
|
|
500
456
|
worse output: a ChatML model under `gemma4` never hits a stop sequence, generates until its limit and then runs the
|
|
501
457
|
tool calls it made up on the way. The first of these that says something wins:
|
|
@@ -517,16 +473,14 @@ tool calls it made up on the way. The first of these that says something wins:
|
|
|
517
473
|
|
|
518
474
|
```yaml
|
|
519
475
|
hosts:
|
|
520
|
-
|
|
476
|
+
box:
|
|
521
477
|
host: 192.0.2.10
|
|
522
478
|
port: 8081
|
|
523
|
-
transport: mlx
|
|
524
479
|
profile: qwen36
|
|
525
480
|
```
|
|
526
481
|
|
|
527
482
|
4. The server's chat template, on `llama_cpp` hosts: `/props` (asked with `?model=`, which a router needs) with
|
|
528
483
|
`<|im_start|>` and `<function=` is `qwen36`, any other ChatML template too; `<|turn>` and `<|tool_call>` is `gemma4`.
|
|
529
|
-
mlx_lm.server and oMLX publish no template, so config or the name decides there.
|
|
530
484
|
5. The name: `qwen` → `qwen36`, `gemma` → `gemma4`.
|
|
531
485
|
6. `qwen36`.
|
|
532
486
|
|
|
@@ -669,7 +623,7 @@ models:
|
|
|
669
623
|
|
|
670
624
|
| Backend | A level change | Asks |
|
|
671
625
|
|---|---|---|
|
|
672
|
-
| native Qwen (`qwen36`) | only the prompt's tail (the
|
|
626
|
+
| native Qwen (`qwen36`) | only the prompt's tail (the thought after the cue) | no |
|
|
673
627
|
| native Gemma 4 (`gemma4`) | the whole prompt is read again: `<\|think\|>` starts the system prompt | yes |
|
|
674
628
|
| chat host, local (llama.cpp, Splash), a model name with `gemma` | the whole prompt: the template writes the same token | yes |
|
|
675
629
|
| chat host, local, any other model | other request fields; the server keeps its cache, only the tail changes | no |
|
|
@@ -692,12 +646,24 @@ What each backend gets:
|
|
|
692
646
|
|
|
693
647
|
| Backend | `off` | `low` / `medium` / `high` |
|
|
694
648
|
|---|---|---|
|
|
695
|
-
| native (`/completion`), `qwen36` | an empty thought after the assistant cue
|
|
649
|
+
| native (`/completion`), `qwen36` | an empty thought after the assistant cue | no knob: an open thought after the cue where the template has one (below), the same at each effort, one notice |
|
|
696
650
|
| native, `gemma4` | no `<\|think\|>` token at the start of the system prompt | no knob, one notice |
|
|
697
651
|
| chat host (`api: openai`) | `chat_template_kwargs: {enable_thinking: false}` and `reasoning_effort: "none"` | `reasoning_effort: <level>` |
|
|
698
652
|
| Messages host (`api: anthropic`) | `thinking: {type: "disabled"}` | `thinking: {type: "adaptive", display: "summarized"}` and `output_config: {effort: <level>}` |
|
|
699
653
|
| Messages host, `dialect: fireworks` | `thinking: {type: "disabled"}` | `thinking: {type: "adaptive"}` and `output_config: {effort: <level>}` (no `display`) |
|
|
700
654
|
|
|
655
|
+
Native `qwen36` at `default`, `low`, `medium` or `high` gets an open thought, `<think>` and a newline, after the
|
|
656
|
+
assistant cue when the llama.cpp server's chat template writes one there (Qwen3.5 and later: its generation prompt
|
|
657
|
+
ends `'<think>\n'` unless `enable_thinking` is false), read from the model's `/props` (`?model=`, which a llama.cpp
|
|
658
|
+
router answers from the model's own server): the model thinks, as the template would have it, and its reasoning shows
|
|
659
|
+
as thinking, not in the answer. Without it Qwen3.6 writes an empty thought at once and never thinks. A template whose
|
|
660
|
+
own default is no thinking (Qwen3.5's small models think only with `enable_thinking` true) gets the open thought
|
|
661
|
+
too: set `thinking: off` for such a model (`models:` or `hosts:`) to keep its answers immediate. A tool call written
|
|
662
|
+
inside a closed thought is not run. With no template
|
|
663
|
+
in `/props` (an older Qwen3 or other ChatML template, a router that can't load the model, a failed probe) the prompt
|
|
664
|
+
ends at the cue as before. `low`, `medium` and `high` think the same: native Qwen has no effort knob, and chi says
|
|
665
|
+
so once (`low isn't supported by native qwen36 on HOST; it thinks, with no effort control`).
|
|
666
|
+
|
|
701
667
|
On a Messages host `default` sends nothing: the provider's own default, which differs by model (some Claude models
|
|
702
668
|
think adaptively but show no thinking text, their blocks still going back; others, and Splash, don't think at all). A model that refuses `disabled` and names `between_tools` in the 400 (Claude 5.x) gets `thinking: {type:
|
|
703
669
|
"between_tools"}` wherever `off` asked for `disabled`, from then on: the adapter keeps that per model for as long as it
|
|
@@ -737,8 +703,8 @@ hosts:
|
|
|
737
703
|
sampling: { reasoning_effort: null } # sends only enable_thinking: false
|
|
738
704
|
```
|
|
739
705
|
|
|
740
|
-
A different level changes a native
|
|
741
|
-
|
|
706
|
+
A different level changes a native Gemma's system prompt (its token), so the next turn reads the whole context again
|
|
707
|
+
once; on native Qwen and a chat host only the end of the prompt changes.
|
|
742
708
|
|
|
743
709
|
## Reasoning history
|
|
744
710
|
|
|
@@ -1286,7 +1252,6 @@ Whether a model can see images is found out before a turn with images is sent:
|
|
|
1286
1252
|
- a native llama.cpp host: `/props` must report `modalities.vision` (the server
|
|
1287
1253
|
runs with `--mmproj`) and a media marker, and the prompt profile must know the
|
|
1288
1254
|
chat template's image wrapping (qwen36 and gemma4 do);
|
|
1289
|
-
- mlx and oMLX hosts: no (not verified with recent chi versions);
|
|
1290
1255
|
- an OpenAI-API host: a local llama.cpp's `/props`, else the host's model list
|
|
1291
1256
|
(OpenRouter's `architecture.input_modalities`); when it doesn't say, the image
|
|
1292
1257
|
is sent and a refusal is reported.
|
|
@@ -1598,7 +1563,6 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
1598
1563
|
| `default.input` | none | | Text pre-filled at the first prompt (a trailing space is kept); `--no-default-input` skips it. See [CLI](cli.md). |
|
|
1599
1564
|
| `default.max_tokens` | server's | yes | Most tokens one generation may produce (`n_predict` on a native llama.cpp host; OpenRouter: 32768 when unset). |
|
|
1600
1565
|
| `model.profile` | none | `--profile` | Prompt profile for every model (`qwen36`, `gemma4`); env and CLI only. See "Prompt profile". |
|
|
1601
|
-
| `server.transport` | `llama_cpp` | yes | `llama_cpp`, `mlx` or `omlx`; see "Model Server Transport". |
|
|
1602
1566
|
| `server.host` | `localhost` | yes | The model server when there is no `hosts:` map. |
|
|
1603
1567
|
| `server.port` | `8080` | yes | Its port. |
|
|
1604
1568
|
| `server.open_timeout` | `10` | yes | Connection timeout, seconds. |
|
|
@@ -1662,7 +1626,6 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
1662
1626
|
| `context.peer_turns` | `3` | yes | Wake turns in a row a peer agent's answers may start (a peer bundle's outbox source, such as `codex-outbox`), with no message typed in the web or a TUI in between (`chi send` doesn't count); within `session.max_wakes`. Then answers wait as notes, and the UIs hear it once. An answer whose summary ends its verdicts with `VERDICT OK` wakes once to close the round, counted toward `session.max_wakes` only. `0`: a peer source wakes like any other. See [Waking](context.md#waking). |
|
|
1663
1627
|
| `toolkit.default` | `full` | | The toolkit a new session is stored on: the tools its prompts declare (`full`, `lean`, `child`, one of `toolkit.kits` or a bundle's). `chi --toolkit` picks one per session. See "Toolkits". |
|
|
1664
1628
|
| `toolkit.child` | `full` | | A delegate child's toolkit, unless its `delegate` call names one (`toolkit:`). See "Toolkits". |
|
|
1665
|
-
| `thinking.turn_preamble` | `true` | yes | Ask a `qwen36` model to open its thinking with a short `TURN:` line (the step label). |
|
|
1666
1629
|
| `reasoning_history` | `turn` | | Which of its reasoning a chat host's model gets back: `drop`, `turn`, `all`; environment only (`SAMAGOTCHI_REASONING_HISTORY`), below a session's own level (`/reasoning-history`), above the `models:`/`hosts:` entries. See "Reasoning history". |
|
|
1667
1630
|
| `thinking.level` | `default` | `--thinking` | `off`, `low`, `medium`, `high` or `default` for every model; the flag and env outrank the `models:`/`hosts:` entries, the file's value doesn't; a session's own level outranks them all, and `--thinking` sets it on a session run (on `chi web` it sets this). See "Thinking". |
|
|
1668
1631
|
| `models.<key>.thinking`, `hosts.<name>.thinking` | none | | A model's or host's level. See "Thinking". |
|
data/docs/hooks.md
CHANGED
|
@@ -178,7 +178,7 @@ are also logged (`turn` tag, `hook_notice`).
|
|
|
178
178
|
|
|
179
179
|
`:generation_progress` sees a model response **while it streams**: the
|
|
180
180
|
thinking and the visible text, on both the raw-prompt path (llama.cpp
|
|
181
|
-
`/completion
|
|
181
|
+
`/completion`) and the chat path (`api: openai`). It fires
|
|
182
182
|
for the turn's own generations only (not a plugin's `ctx.ask_model`, not a
|
|
183
183
|
recap).
|
|
184
184
|
|
|
@@ -115,6 +115,6 @@ Context window lookup (`ContextWindow.resolve`, the first that answers wins; the
|
|
|
115
115
|
4. `context.window_tokens`: CLI, `SAMAGOTCHI_CONTEXT_WINDOW_TOKENS` or the config file (`:config` or `:env`).
|
|
116
116
|
5. `ContextWindow::DEFAULT_TOKENS`, 256000 (`:default`).
|
|
117
117
|
|
|
118
|
-
Settings (3 and 4) only fill in when the server and its list report nothing (
|
|
118
|
+
Settings (3 and 4) only fill in when the server and its list report nothing (a server that is down, a chat host whose list has no window). A
|
|
119
119
|
window the stream's `usage` reports itself (`n_ctx` / `context_window`) wins over all of these for that request
|
|
120
120
|
(`window_src=server`). `chi self` shows the offline answer (steps 3 to 5) and which setting gave it.
|
data/docs/internals/engine.md
CHANGED
|
@@ -165,8 +165,7 @@ whose tools, hooks, gate and dispatch it uses.
|
|
|
165
165
|
|
|
166
166
|
### KernelLoop (the native, raw-prompt loop)
|
|
167
167
|
|
|
168
|
-
Runs models whose server takes a raw prompt (llama.cpp `/completion
|
|
169
|
-
OpenAI-compatible completions server). chi formats the prompt itself, per model profile (Gemma 4, Qwen 3.6), and
|
|
168
|
+
Runs models whose server takes a raw prompt (llama.cpp `/completion`). chi formats the prompt itself, per model profile (Gemma 4, Qwen 3.6), and
|
|
170
169
|
parses tool calls out of the generated text.
|
|
171
170
|
|
|
172
171
|
```
|
|
@@ -684,8 +683,12 @@ Both loops:
|
|
|
684
683
|
|
|
685
684
|
Native loop only: the Qwen open-block recovery, and the thinking prefill (`Thinking.native`: with thinking off, Qwen
|
|
686
685
|
gets an empty thought after the cue, `QWEN_EMPTY_THOUGHT`, and Gemma 4 its empty thought channel,
|
|
687
|
-
`GEMMA_EMPTY_THOUGHT`, with no `<|think|>` token before the system prompt;
|
|
688
|
-
|
|
686
|
+
`GEMMA_EMPTY_THOUGHT`, with no `<|think|>` token before the system prompt; at any other level Qwen gets an open
|
|
687
|
+
thought, `QWEN_OPEN_THOUGHT` (`<think>\n`), when the server's chat template writes one after its cue
|
|
688
|
+
(`Thinking.opens_thought?` on the model's `/props`, a Qwen3.5+ template; `KernelLoop#native_prefill`, once per turn).
|
|
689
|
+
The prefill is kept in the model message so the next prompt starts with what the server has cached; each
|
|
690
|
+
generation's `ThoughtStreamSplitter` is seeded with it (an open thought's reasoning streams as `thinking:`), and the
|
|
691
|
+
answer is stripped from prefill + response, so the reasoning never lands in it).
|
|
689
692
|
|
|
690
693
|
## Key design principles
|
|
691
694
|
|
data/docs/plugins.md
CHANGED
|
@@ -966,7 +966,7 @@ id = ctx.sessions.fork(messages: ctx.messages + [{ role: "user", content: q }, {
|
|
|
966
966
|
title: "btw: #{q}")
|
|
967
967
|
ctx.sessions.send(id, "go on from here")
|
|
968
968
|
ctx.sessions.send(id, "then the docs", delivery: :queue) # after its running turn
|
|
969
|
-
ctx.sessions.read(id) # => {id:, title:, status:, parent_id:, running:, messages:}
|
|
969
|
+
ctx.sessions.read(id) # => {id:, title:, status:, parent_id:, running:, messages:} (title: its /title, else its preview)
|
|
970
970
|
ctx.sessions.children # => [{id:, short_id:, state:, branch:, last_reply:, reported:, ...}]
|
|
971
971
|
ctx.sessions.stop(id) # one of this session's own children
|
|
972
972
|
```
|
|
@@ -977,8 +977,9 @@ ctx.sessions.stop(id) # one of this session's own children
|
|
|
977
977
|
attach to it. It returns the child's id.
|
|
978
978
|
- Without `prompt:` the child waits idle. With one, it runs it as its first
|
|
979
979
|
turn, and counts against `session.max_children` (like `delegate`).
|
|
980
|
-
- `title:` is what the lists show
|
|
981
|
-
the first user message).
|
|
980
|
+
- `title:` is its preview: what the lists show for it (else the prompt, or
|
|
981
|
+
the first user message). It stays after the first turn; it is not a
|
|
982
|
+
`/title` of its own (see [Session titles](sessions.md#session-titles)).
|
|
982
983
|
- An image a message names is copied into the child. One whose file is
|
|
983
984
|
gone is dropped, with `[image x.png was not copied]` in its message.
|
|
984
985
|
- `send(id, text)` sends a user message to a session (an id or a unique
|
data/docs/sessions.md
CHANGED
|
@@ -217,13 +217,19 @@ chi send --new --continues last:3fa2 -m "new day" # the next link of 3fa2's
|
|
|
217
217
|
- The 16 KiB cap applies: `git diff | chi send --new …` on a big diff is refused; name the branch in the message instead and let the session read it.
|
|
218
218
|
- A session nobody attaches to stalls at its first question or guardrail ask until someone answers it (`chi answer`, the web, `chi --attach`), and its worker stays up meanwhile: it doesn't idle-exit while a question waits (`chi sessions list` shows it as `waiting`; `chi sessions stop ID` frees it). Otherwise it idle-exits after `session.idle_exit_minutes` like any worker. These are ordinary sessions: delete them like any other.
|
|
219
219
|
|
|
220
|
+
## Session titles
|
|
221
|
+
|
|
222
|
+
A session is named by its first message until you give it a title: `/title Coordinator` (in the REPL, an attached terminal, the web composer or `chi send ID "/title Coordinator"`; a parent agent may name its delegates the same way). The title is saved in the session file (`title`), whitespace collapsed and cut at 80 characters, and runs mid-turn too, at once. `/title` alone shows it (`title: none (shows the first message: hey chi …)` without one); `/title --clear` drops it, and the first message names the session again. A session whose first line was the command itself (`chi send --new "/title X"`) takes its preview from its first real message.
|
|
223
|
+
|
|
224
|
+
The title names the session wherever its first message did: `chi sessions list` (plain, `--live`, tsv and json's `desc`, before the recap; json has `title` too), `chi sessions delete`'s line, the `list_sessions` tool, `chi broadcast`, the desktop helper, a parent's children (the coordinator bundle's `/children` card, `ctx.sessions.children`; `ctx.sessions.read` too), and in the web the cards, the families and chain rows, the top bar and the browser tab, the notifications and the search. The web's info bar has `✎` (Rename this session): a small input prefilled with the title; Enter or a click elsewhere sends `/title TEXT` when it changed (empty: `/title --clear`), Esc closes it unsent. A session a chi REPL owns has no `✎`. A continue's new link keeps the title (see [Session chains](#session-chains)). An older `chi web` (or any chi from before the field) that saves a session drops its title: restart `chi web` after upgrading.
|
|
225
|
+
|
|
220
226
|
## Session chains
|
|
221
227
|
|
|
222
228
|
A session can **continue** an earlier one: "the next run of the same routine" (a daily coordinator, say, continued once a day or several times). The new session's `continues` field names the previous link; the chain is derived from it, never stored: follow `continues` back for the earlier links, forward for the later ones. It is not a parent: archiving one link leaves the next alone, and lists fold delegates, not links. A link is continued at most once, so a chain has no forks and its latest link is the one nobody continues.
|
|
223
229
|
|
|
224
230
|
`SessionManager.continue_session` makes the next link:
|
|
225
231
|
|
|
226
|
-
- **What it copies:** the previous link's folder, its model (the name it was typed as, so an alias holds) unless the continue names one (`--model`, the web's `model`), and its own LLM context values. A model given is checked like a new session's (a host config.yml doesn't have refuses the continue, nothing archived). An inherited model whose host can't run it any more (gone from config.yml, `enabled: false`, an alias naming another host) **falls back to the default model** with a warning, `gone:org/model can't run (unknown host 'gone' in model …); started on <default>`, decided before the archive; with no default model configured the continue is refused. The carry note doesn't mention the model. The new link carries no transcript, so a different model, or another provider, starts clean there. Its preview is the previous link's (until its first message is saved), unless it starts with a message.
|
|
232
|
+
- **What it copies:** the previous link's folder, its model (the name it was typed as, so an alias holds) unless the continue names one (`--model`, the web's `model`), and its own LLM context values. A model given is checked like a new session's (a host config.yml doesn't have refuses the continue, nothing archived). An inherited model whose host can't run it any more (gone from config.yml, `enabled: false`, an alias naming another host) **falls back to the default model** with a warning, `gone:org/model can't run (unknown host 'gone' in model …); started on <default>`, decided before the archive; with no default model configured the continue is refused. The carry note doesn't mention the model. The new link carries no transcript, so a different model, or another provider, starts clean there. Its preview is the previous link's (until its first message is saved), unless it starts with a message. Its title (`/title`) is the previous link's, with or without a first message.
|
|
227
233
|
- **The carry note:** the new link starts with a context note from `session chain` (shown in its transcript as `note from session chain`), queued before its worker spawns, so its first turn sees it: `This session continues 3fa2c1d0 (2026-10-08 09:12), the previous link of its chain. Its recap, as of 2026-10-08 18:02:` and the previous link's recap (`recap.json`; cut at 8000 characters). No recap (fewer user turns than a recap needs, recaps off, none written in time): `… It left no recap.` Never more than the recap: no transcript. An idle new link holding only the note is kept, not discarded as empty.
|
|
228
234
|
- **The recap first:** a previous link with a live worker is asked for a recap (the Bridge's `POST /recap`) and given up to 30 s to write it; one with none wrote its recap when its worker left.
|
|
229
235
|
- **The previous link is archived** before the new one starts, by the archive's own rules (see *Archiving a session*): a turn running, a prompt queued or the step-limit question refuse it, a live idle worker is stopped, a plain REPL's session is refused. Its finished delegates are archived with it, as any archive does.
|
|
@@ -231,7 +237,7 @@ A session can **continue** an earlier one: "the next run of the same routine" (a
|
|
|
231
237
|
- **If the continue fails part-way:** a new link that fails to start gives its moved delegates back before it is deleted (each gets a note saying so; its worker is stopped first, if one started, and a worker that won't stop is reported, the link left), with any report one of them rang it with meanwhile; the previous link, back in the lists, is woken for reports waiting. A continue that dies in the middle leaves a `move.json` in the new link's folder: the new link's next worker finishes that move (only the delegates it names, and without a second note to one already told) as it starts. Until the new link's own worker holds it, a ring from a moved delegate doesn't start another (a `starting` marker in its folder, which that worker removes; a stale one, over 60 s old or from a process that is gone, is ignored). An open delegate below an archived delegate refuses the continue (`under archived …`): unarchive that one, or archive this one.
|
|
232
238
|
- **Refused too:** a link that is continued already (naming the link that continues it; continue that one, or `last:<id>`), a previous link whose folder is gone, and one whose model's host can't run it when no default model is configured to fall back to. A refusal starts nothing and archives nothing; a new link that fails to start puts back what was archived. Every refusal that can be known up front (the archive's ones included) comes before the recap wait. One continue of a session runs at a time (a lock in its folder; other chains don't wait), so a double click finds the first one's link and is refused, while a `last:<id>` continue that waited follows the chain to its new end.
|
|
233
239
|
- **Web:** `POST /api/sessions` takes `continues: "<id, prefix or last:id>"`, with `idle: true` (a new link waiting for its first message, previewed by the previous link's, or `preview`) or a `prompt`, and `model` (or `model_name`) for the new link's model, but not `dir`, `llm_context` or `thinking` (400 `invalid_continues`: the previous link's are taken). 201 is the new session's summary, `continues` set, with `moved`: the ids of the delegates that moved to it (`[]` for none), and `model_warning`: the default-model fallback's warning, then any about the model id, joined by `; ` (`null` for none), and `opener`: the previous link's first typed message, in full, when that link continues one itself (`null` otherwise: a chain's first link's first message is its task); 404 `not_found`; 400 `ambiguous_id` for a prefix of several sessions, `invalid_model` for a given model whose host config.yml doesn't have (or a fallback with no default model); 409 `continued` with `next_id` (the link that continues it already, which the page opens instead), `open_children` with `ids` (open delegates that can't move), `folder_gone`, `busy`, `scratch` or `owned_by_tui`. Every session summary carries `continues`.
|
|
234
|
-
- **The page:** the info bar's `continue →` continues the open session (`continues: <id>, idle: true`; it reads `continuing…` through the recap wait) and opens the new link with an opener in the composer, not sent (a draft already there stays instead): the previous link's own first typed message when that link continues one (the 201's `opener`; a `!shell` line isn't taken), else `Continue where we left off.`. So whatever you sent on the last link comes back on the next one, and editing it once changes it for every later link; the previous link is archived, a toast says so (`Continued 3fa2c1d0 in 4b7e9a01; 3fa2c1d0 is archived; 1 open delegate moved`), and a moved delegate's card folds under the new link. When the previous link's model's host is gone the link starts on the default model and the toast carries the server's `model_warning` too, and then stays until closed. A fresh link (one with no turn yet, however it was made: `continue →`, `chi send --new --continues`, a reload) shows the model picker in its composer with the link's model, until its first turn starts (a failed or canceled first turn ends it too); a pick runs `/model <name>` in the link (a command bubble; the info bar follows) and changes neither the start page's remembered model nor its Recent list, and a Send right after it waits for the `/model` to be taken. A link a chi REPL owns, or one continued already, shows none. A link continued already has no `continue →`: its `→` link goes to the next link (a stale page's continue answered 409 `continued` opens `next_id`). A continue refused for open delegates that can't move shows a toast that names each one (its preview and what the server says to do: `older chi worker 0.46.1; restart it`, `open in a chi REPL; close it there`), each a link to its page; any other refusal shows the server's words. The info bar of a link carries `← 2026-10-08` (the previous link's day) and, on an earlier link, `2026-10-10 →`, each a link with its id, its full start (`2026-10-10 14:32`) and its preview in the tooltip; a neighbour that started on the open link's own day shows its time instead (`← 09:12`, `14:32 →`). A link made by `continue →` inherits the previous link's title when it is created, so its card keeps its chain's title whatever opener it is sent, and its top bar and browser tab show its card's title. A link whose title is the opener and nothing else (a `chi send --new --continues` link whose first message is `Continue where we left off.`) shows its chain's first link's title instead, on its card, in the popover and in its info bar's title line, display only (its messages and data keep the opener; its own title when the first link is gone). The lists fold a chain into the card of its latest listed link, whatever the earlier links' archived state (typing in an archived link brings it back, folded there still), their delegates with them: the card's `↩ #N` chip (`#N of M` when later links aren't listed; its tooltip `Session chain: link N of M`) opens a popover of the other links, newest first (`#N`, its date, with its time (`2026-10-17 14:32`) when another link of the chain started the same day, its recap's first sentence
|
|
240
|
+
- **The page:** the info bar's `continue →` continues the open session (`continues: <id>, idle: true`; it reads `continuing…` through the recap wait) and opens the new link with an opener in the composer, not sent (a draft already there stays instead): the previous link's own first typed message when that link continues one (the 201's `opener`; a `!shell` line isn't taken), else `Continue where we left off.`. So whatever you sent on the last link comes back on the next one, and editing it once changes it for every later link; the previous link is archived, a toast says so (`Continued 3fa2c1d0 in 4b7e9a01; 3fa2c1d0 is archived; 1 open delegate moved`), and a moved delegate's card folds under the new link. When the previous link's model's host is gone the link starts on the default model and the toast carries the server's `model_warning` too, and then stays until closed. A fresh link (one with no turn yet, however it was made: `continue →`, `chi send --new --continues`, a reload) shows the model picker in its composer with the link's model, until its first turn starts (a failed or canceled first turn ends it too); a pick runs `/model <name>` in the link (a command bubble; the info bar follows) and changes neither the start page's remembered model nor its Recent list, and a Send right after it waits for the `/model` to be taken. A link a chi REPL owns, or one continued already, shows none. A link continued already has no `continue →`: its `→` link goes to the next link (a stale page's continue answered 409 `continued` opens `next_id`). A continue refused for open delegates that can't move shows a toast that names each one (its preview and what the server says to do: `older chi worker 0.46.1; restart it`, `open in a chi REPL; close it there`), each a link to its page; any other refusal shows the server's words. The info bar of a link carries `← 2026-10-08` (the previous link's day) and, on an earlier link, `2026-10-10 →`, each a link with its id, its full start (`2026-10-10 14:32`) and its preview in the tooltip; a neighbour that started on the open link's own day shows its time instead (`← 09:12`, `14:32 →`). A link made by `continue →` inherits the previous link's preview (and its title, if it has one) when it is created, so its card keeps its chain's title whatever opener it is sent, and its top bar and browser tab show its card's title. A link whose title is the opener and nothing else (a `chi send --new --continues` link whose first message is `Continue where we left off.`) shows its chain's first link's title instead, on its card, in the popover and in its info bar's title line, display only (its messages and data keep the opener; its own title when the first link is gone). The lists fold a chain into the card of its latest listed link, whatever the earlier links' archived state (typing in an archived link brings it back, folded there still), their delegates with them: the card's `↩ #N` chip (`#N of M` when later links aren't listed; its tooltip `Session chain: link N of M`) opens a popover of the other links, newest first (`#N`, its date, with its time (`2026-10-17 14:32`) when another link of the chain started the same day, its own title when it differs from the card's (a link inherits its chain's, so mostly it doesn't), else its recap's first sentence, else its preview, `archived`), a click opens one; the chip shows a live dot or `waiting` for a link that has a worker or waits on the user, and a waiting link puts its chain's card first. All sessions folds nothing while a search is typed or "include archived" is on: every link is a card of its own then, its chip `#N of M`, with its own delegates folded in, so a search finds an earlier link and its delegates.
|
|
235
241
|
- **CLI:** `chi send --new --continues (ID|PREFIX|last:ID) [--model M] [-m TEXT]` (see *Starting a session*).
|
|
236
242
|
- **Which link:** an id, a unique prefix, or `last:<id or prefix>`, the latest link of the chain that session is in. It walks the links that still exist: a deleted link in the middle (`chi sessions delete`, the retention sweep of an unarchived one) ends the walk before it, so `last:` of an earlier link then names that earlier link, and continuing it starts a second line beside the links after the gap; name a link after the gap instead. A trigger names the chain this way: `chi send` to an archived link would bring it back (a human's input), not continue the chain.
|
|
237
243
|
|