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