samagotchi 0.85.0 → 0.87.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 +76 -1
- data/bin/chi +39 -2
- data/docs/architecture.md +36 -5
- data/docs/bundle-authoring.md +125 -0
- data/docs/cli.md +51 -3
- data/docs/configuration.md +6 -5
- data/docs/guardrails.md +112 -39
- data/docs/hooks.md +19 -3
- data/docs/internals/anthropic.md +2 -2
- data/docs/internals/bundles.md +11 -2
- data/docs/internals/context-telemetry.md +19 -1
- data/docs/internals/engine.md +1 -1
- data/docs/memory.md +1 -1
- data/docs/plugins.md +59 -2
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +19 -7
- data/docs/sub-agent.md +39 -25
- data/docs/testing.md +7 -2
- data/lib/samagotchi/answer_command.rb +21 -25
- data/lib/samagotchi/bridge/client_event.rb +50 -0
- data/lib/samagotchi/bridge/sse_writer.rb +6 -2
- data/lib/samagotchi/bridge.rb +22 -11
- data/lib/samagotchi/bridge_client.rb +2 -2
- data/lib/samagotchi/bundle_command/check.rb +348 -0
- data/lib/samagotchi/bundle_command/scaffold.rb +135 -0
- data/lib/samagotchi/bundle_command/test_run.rb +89 -0
- data/lib/samagotchi/bundle_command.rb +125 -2
- data/lib/samagotchi/bundles/core/manifest.yml +2 -2
- data/lib/samagotchi/bundles/cost-cap/manifest.yml +4 -4
- data/lib/samagotchi/bundles/cost-cap/plugin.rb +4 -1
- data/lib/samagotchi/bundles/dev/manifest.yml +2 -1
- data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +4 -2
- data/lib/samagotchi/bundles/guardrails/manifest.yml +1 -1
- data/lib/samagotchi/bundles/known-names/hooks/known_names.rb +3 -3
- data/lib/samagotchi/bundles/known-names/manifest.yml +2 -2
- data/lib/samagotchi/bundles/ruby/manifest.yml +11 -0
- data/lib/samagotchi/bundles/ruby/plugin.rb +74 -0
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +3 -3
- data/lib/samagotchi/bundles/system/self_map.md +2 -1
- data/lib/samagotchi/cli/parent_wait.rb +3 -1
- data/lib/samagotchi/client_id.rb +1 -1
- data/lib/samagotchi/config.rb +195 -115
- data/lib/samagotchi/context_status.rb +1 -1
- data/lib/samagotchi/continue_offer.rb +2 -1
- data/lib/samagotchi/engine.rb +4 -4
- data/lib/samagotchi/guardrail_wiring.rb +4 -0
- data/lib/samagotchi/guardrails/approval.rb +12 -2
- data/lib/samagotchi/guardrails/load_failures.rb +17 -3
- data/lib/samagotchi/guardrails/parent_approvals.rb +14 -70
- data/lib/samagotchi/guardrails/parent_policy.rb +327 -0
- data/lib/samagotchi/guardrails/verdict.rb +6 -3
- data/lib/samagotchi/hook_routes.rb +5 -3
- data/lib/samagotchi/hooks/bundle_loader.rb +3 -1
- data/lib/samagotchi/hooks/registry.rb +13 -6
- data/lib/samagotchi/hooks.rb +5 -0
- data/lib/samagotchi/kernel_loop.rb +5 -2
- data/lib/samagotchi/llm/anthropic_assembly.rb +1 -1
- data/lib/samagotchi/memory_bundle/shas.rb +127 -0
- data/lib/samagotchi/memory_bundle.rb +1 -0
- data/lib/samagotchi/parent_answers_command.rb +80 -0
- data/lib/samagotchi/parent_report.rb +122 -30
- data/lib/samagotchi/pending_question.rb +8 -4
- data/lib/samagotchi/plugin/api.rb +16 -0
- data/lib/samagotchi/plugin/context.rb +11 -5
- data/lib/samagotchi/plugin/loader.rb +5 -3
- data/lib/samagotchi/question_desk.rb +39 -25
- data/lib/samagotchi/queued_command.rb +5 -4
- data/lib/samagotchi/relay.rb +5 -1
- data/lib/samagotchi/relay_verifier.rb +4 -5
- data/lib/samagotchi/reply_wait.rb +14 -5
- data/lib/samagotchi/send_command.rb +103 -12
- data/lib/samagotchi/session.rb +26 -2
- data/lib/samagotchi/session_commands.rb +45 -8
- data/lib/samagotchi/session_setup.rb +10 -8
- data/lib/samagotchi/terminal_ui/attach_launcher.rb +18 -8
- data/lib/samagotchi/terminal_ui/attached_loop.rb +25 -12
- data/lib/samagotchi/terminal_ui/thinking_line.rb +5 -9
- data/lib/samagotchi/terminal_ui.rb +20 -11
- data/lib/samagotchi/testing/boundary.rb +0 -3
- data/lib/samagotchi/testing/bundle.rb +7 -0
- data/lib/samagotchi/testing/host.rb +6 -4
- data/lib/samagotchi/testing/install.rb +119 -0
- data/lib/samagotchi/testing/recorder.rb +13 -4
- data/lib/samagotchi/testing.rb +1 -0
- data/lib/samagotchi/tools/builtins.rb +2 -1
- data/lib/samagotchi/tools/delegate.rb +4 -1
- data/lib/samagotchi/tools/delegate_wait.rb +16 -7
- data/lib/samagotchi/tools/task_runtime.rb +5 -3
- data/lib/samagotchi/turn_checkpoint.rb +64 -2
- data/lib/samagotchi/turn_flow.rb +10 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +48 -10
- data/lib/samagotchi/web/public/chunk_router.js +8 -19
- data/lib/samagotchi/web/public/ctx.js +12 -26
- data/lib/samagotchi/web/public/stream_handlers.js +1 -1
- data/lib/samagotchi/web/server.rb +4 -0
- data/lib/samagotchi/worker.rb +27 -5
- data/lib/samagotchi/worker_inbound.rb +4 -4
- metadata +12 -2
- data/lib/samagotchi/guardrails/parent_continue.rb +0 -43
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1bfd7070b1c55cbb0e0647a79ec729996a78318ca2b2f4ccbf19c24ce7240eb9
|
|
4
|
+
data.tar.gz: 5fa4d754f3c9bb79767978d3b9fa6ac1c7091c84897a9043f3ad3129bd98c24c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 787b7debd71f8a408ffaa801bd7d352de1f3769d018959b35e3933b16641efb194220630482d14f5ec32d4c50b5fce8f56c8a78b88a1fc1c2de58c2e05e6b361
|
|
7
|
+
data.tar.gz: 5f3759bd7c6e1e9fea4e5b7af8eab1c107d5296f9eba85b0b99fd42381c5a1c3dbb01b7efb86b52ec14b11958333110a97cfab80b5d8fd5a3a1489cab2f50bdd
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,79 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.87.0] - 2026-10-19
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- A session's own `parent_answers`, tighten-only: `chi --parent-answers LEVEL` and `chi send --new --parent-answers
|
|
16
|
+
LEVEL` save it in the new session (kept by respawns, forks and continues; a `delegate` child starts with its
|
|
17
|
+
parent's), and what a parent agent may answer there is the stricter of it and config.yml's
|
|
18
|
+
`guardrails.parent_answers`, in `chi answer`, the worker and the guidance. `/parent-answers` shows and sets it;
|
|
19
|
+
anyone may tighten it, only you loosen it, at the REPL, an attached terminal or the web.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- The known-names hook treats `#` URL fragments as token boundaries, allowing exact protected names in URLs while
|
|
24
|
+
still rejecting misspellings before a fragment.
|
|
25
|
+
|
|
26
|
+
## [0.86.0] - 2026-10-18
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- `chi bundle new NAME`, `chi bundle check [PATH] [--fix]` and `chi bundle test [PATH]` for writing a bundle: a
|
|
31
|
+
scaffold that checks clean and passes its test; a check that installs and loads the bundle in a sandbox and prints
|
|
32
|
+
one finding per line with a fix hint (manifest, sha256 lines, load errors with file:line, `requires_chi` floor,
|
|
33
|
+
trust, what it registers), `--fix` filling the sha lines; its tests run on chi's own Ruby and lib. `check` and
|
|
34
|
+
`test` run the bundle's code in their process: run them on code you'd install. docs/bundle-authoring.md (shipped
|
|
35
|
+
in the gem) is the first read.
|
|
36
|
+
- chi tells you when a changed config.yml setting applies only after a restart ("server.port changed — applies after
|
|
37
|
+
a worker restart"; chi web's settings: after restarting chi web).
|
|
38
|
+
- The `ruby` bundle (`chi bundle install ruby`, or the `dev` profile): an `execute` that ran RSpec and found no
|
|
39
|
+
examples at all (`0 examples, 0 failures`: a path, a `-e`/`--tag` filter or a `.rspec --pattern` that matched
|
|
40
|
+
nothing) steers a hint into the turn instead of leaving the run looking green.
|
|
41
|
+
- A plugin's or a hook's `ask_user` takes `effects:` (option label => `:spend`, `:permit`, `:permit_wide`, `:deny`;
|
|
42
|
+
untagged = `:answer`), so a parent agent's answer is held to `guardrails.parent_answers`. The bundle test kit
|
|
43
|
+
records them (`bundle.questions.last.effects`). cost-cap 0.2.0 tags its Continue `:spend` and Stop `:deny`: under
|
|
44
|
+
`hard` a parent may only Stop it.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- **What a parent agent may answer is one setting, `guardrails.parent_answers`** (`hard`, `standard`, `soft`), by
|
|
49
|
+
each option's effect, for every question: approvals, the step-limit Continue, relayed delegate approvals and plugin
|
|
50
|
+
and hook questions. **The default `soft` lets a parent give "Allow once"** (before, a parent could only deny an
|
|
51
|
+
approval); `standard` is the old default (deny, and Continue), `hard` lets a parent only deny, Stop or answer
|
|
52
|
+
plainly. A wider scope and any allow on chi's own config stay the user's. `chi send --wait`, `chi answer` and
|
|
53
|
+
`delegate_result` list what the parent may give. An invalid value warns and is `hard`. A parent's `/continue yes`
|
|
54
|
+
(`chi send ID -m '/continue yes'`, a piped `chi --attach`) is held to it too, as chi answer's.
|
|
55
|
+
- SSE chunks carry chi's normalized `counts` instead of the provider's raw `payload` (and no `content`). An external
|
|
56
|
+
SSE client reading them moves to `counts` / `text` / `thinking`; `?raw=1` on the worker's stream shows the raw form.
|
|
57
|
+
- At the top context bucket (no forget layer) the model's `[CONTEXT: …]` line asks for a short handoff note (state,
|
|
58
|
+
next step) instead of "summarize aggressively": "context critical — write a short handoff note (state, next step),
|
|
59
|
+
avoid large outputs, delegate broad work to subagents".
|
|
60
|
+
- `guardrails.parent_approvals` and `turn.parent_continue` are removed: config.yml warns about them and names
|
|
61
|
+
`guardrails.parent_answers` (`parent_approvals: once` → `soft`; `turn.parent_continue: false` → `hard`). The old
|
|
62
|
+
pair `parent_approvals: once` with `turn.parent_continue: false` (Allow once but no Continue) has no equivalent:
|
|
63
|
+
the nearest is `hard`, which leaves both to the user.
|
|
64
|
+
- The Bridge's and the relay's 403 for a refused parent answer is `parent_answer_refused`, with its `reason`
|
|
65
|
+
(was `parent_approval_refused` / `parent_continue_refused`).
|
|
66
|
+
- The guardrails bundle's asks before answering chi's questions around `chi answer` (`chi-answer-piped`: `chi
|
|
67
|
+
--attach` or `chi -p` fed from a pipe or a file; `chi-answer-http`: `curl …/answer`) are the user's alone: a parent
|
|
68
|
+
agent's Allow is refused, as on chi's own config (guardrails bundle 0.9.2: its comment says so).
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
|
|
72
|
+
- `chi send --wait` no longer times out when a fallback input file carries a session command: the command's completion is recorded as that input's terminal outcome.
|
|
73
|
+
- An image-only prompt sent from an attached terminal is no longer echoed again as an image line when its turn starts.
|
|
74
|
+
- `chi send --queue --wait` (and a wait for a turn by its id) no longer waits until its timeout when the worker dies
|
|
75
|
+
in the middle of that turn: once the next worker settles it, the wait ends `canceled` with
|
|
76
|
+
`cancel_reason: worker_stopped` ("its worker exited mid-turn"). Another send's wait is not ended by it.
|
|
77
|
+
- `chi web`'s end-of-turn re-read of the answer could keep a plugin's file ref outside the session's folder when
|
|
78
|
+
neither the worker nor the session hub named the folder (an older worker): it now takes the folder from the session
|
|
79
|
+
file, as a reload does, and drops the answer's file refs when no folder is known.
|
|
80
|
+
|
|
81
|
+
Update with `chi update` (the system bundle 0.86.0, core 0.3.3, guardrails 0.9.2, cost-cap 0.2.0, dev 0.6.0 with the new ruby
|
|
82
|
+
bundle 0.1.0), then restart `chi web`.
|
|
83
|
+
|
|
11
84
|
## [0.85.0] - 2026-10-18
|
|
12
85
|
|
|
13
86
|
### Added
|
|
@@ -3408,7 +3481,9 @@ and long-lived sessions.
|
|
|
3408
3481
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
3409
3482
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
3410
3483
|
|
|
3411
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
3484
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.87.0...HEAD
|
|
3485
|
+
[0.87.0]: https://github.com/dm1try/samagotchi/compare/v0.86.0...v0.87.0
|
|
3486
|
+
[0.86.0]: https://github.com/dm1try/samagotchi/compare/v0.85.0...v0.86.0
|
|
3412
3487
|
[0.85.0]: https://github.com/dm1try/samagotchi/compare/v0.84.0...v0.85.0
|
|
3413
3488
|
[0.84.0]: https://github.com/dm1try/samagotchi/compare/v0.83.0...v0.84.0
|
|
3414
3489
|
[0.83.0]: https://github.com/dm1try/samagotchi/compare/v0.82.0...v0.83.0
|
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 --toolkit --tool --no-tool --set --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 --set --parent-answers --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
|
|
@@ -130,6 +130,11 @@ parser = OptionParser.new do |opts|
|
|
|
130
130
|
"saved in the session: --set cost-cap.cap=3)") do |v|
|
|
131
131
|
(options[:sets] ||= []) << v
|
|
132
132
|
end
|
|
133
|
+
opts.on("--parent-answers LEVEL", "What a parent agent may answer on this session's questions: " \
|
|
134
|
+
"hard|standard|soft, only stricter than config.yml's guardrails.parent_answers (saved in the " \
|
|
135
|
+
"session; only you loosen it, with /parent-answers in the terminal or the web)") do |v|
|
|
136
|
+
options[:parent_answers] = v
|
|
137
|
+
end
|
|
133
138
|
opts.on("--memory NAME", "Preload a memory entry into the system prompt (repeatable; a comma list too)") { |m| (options[:memories] ||= []) << m }
|
|
134
139
|
opts.on("--append-system-prompt FILE", "Put FILE's text into the system prompt, after AGENTS.md (repeatable; saved in the session; " \
|
|
135
140
|
"config append_system_prompt: is the baseline)") { |f| (options[:append_system_prompt] ||= []) << f }
|
|
@@ -234,6 +239,22 @@ if options[:sets] && web
|
|
|
234
239
|
warn "Error: --set is a new session's (chi --set, chi send --new --set), not chi web's"
|
|
235
240
|
exit 2
|
|
236
241
|
end
|
|
242
|
+
# --parent-answers: a level in any case (no abbreviations), else a usage
|
|
243
|
+
# error, as chi send's.
|
|
244
|
+
if options[:parent_answers]
|
|
245
|
+
require "samagotchi/guardrails/parent_policy"
|
|
246
|
+
levels = Samagotchi::Guardrails::ParentPolicy::ORDER
|
|
247
|
+
word = options[:parent_answers].strip.downcase
|
|
248
|
+
unless levels.include?(word)
|
|
249
|
+
warn "Error: --parent-answers takes #{levels.join(", ")}, not #{options[:parent_answers]}"
|
|
250
|
+
exit 2
|
|
251
|
+
end
|
|
252
|
+
options[:parent_answers] = word
|
|
253
|
+
end
|
|
254
|
+
if options[:parent_answers] && web
|
|
255
|
+
warn "Error: --parent-answers is a new session's (chi --parent-answers, chi send --new --parent-answers), not chi web's"
|
|
256
|
+
exit 2
|
|
257
|
+
end
|
|
237
258
|
|
|
238
259
|
# A scratch session is new, runs here and goes when it ends.
|
|
239
260
|
if scratch
|
|
@@ -395,6 +416,20 @@ if options[:sets]
|
|
|
395
416
|
options[:toolkit_flags] = (options[:toolkit_flags] || []) + ["--set"]
|
|
396
417
|
end
|
|
397
418
|
|
|
419
|
+
# --parent-answers LEVEL: a new session's own guardrails.parent_answers. An
|
|
420
|
+
# existing session's (--attach/--resume) is ignored with the flags' line,
|
|
421
|
+
# never sent as a /parent-answers command: that would loosen it from a
|
|
422
|
+
# command line, which is the user's to do in a UI.
|
|
423
|
+
if options[:parent_answers]
|
|
424
|
+
if options[:attach] || options[:resume]
|
|
425
|
+
options[:toolkit_flags] = (options[:toolkit_flags] || []) + ["--parent-answers"]
|
|
426
|
+
else
|
|
427
|
+
require "samagotchi/guardrails/parent_policy"
|
|
428
|
+
note = Samagotchi::Guardrails::ParentPolicy.capped_note(options[:parent_answers])
|
|
429
|
+
warn "Warning: --parent-answers #{note}" if note
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
|
|
398
433
|
chi_update_hint.call
|
|
399
434
|
require "samagotchi/launch_mode"
|
|
400
435
|
launch, launch_note = if scratch
|
|
@@ -432,7 +467,8 @@ if launch == :attached
|
|
|
432
467
|
appended_prompt_files: options[:appended_prompt_files] || [],
|
|
433
468
|
toolkit: options[:toolkit_value],
|
|
434
469
|
toolkit_flags: options[:toolkit_flags] || [],
|
|
435
|
-
bundle_settings: options[:bundle_settings] || {}
|
|
470
|
+
bundle_settings: options[:bundle_settings] || {},
|
|
471
|
+
parent_answers: options[:parent_answers])
|
|
436
472
|
rescue Samagotchi::TerminalUI::AttachLauncher::Error => e
|
|
437
473
|
warn "Error: #{e.message}"
|
|
438
474
|
exit 1
|
|
@@ -455,6 +491,7 @@ begin
|
|
|
455
491
|
toolkit: options[:toolkit_value],
|
|
456
492
|
toolkit_flags: options[:toolkit_flags] || [],
|
|
457
493
|
bundle_settings: options[:bundle_settings] || {},
|
|
494
|
+
parent_answers: options[:parent_answers],
|
|
458
495
|
non_interactive: options[:non_interactive],
|
|
459
496
|
model_name: options[:model],
|
|
460
497
|
scratch: scratch
|
data/docs/architecture.md
CHANGED
|
@@ -245,10 +245,33 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
|
|
|
245
245
|
too-old reconnects, and cursors from another worker's epoch, receive a `reset` marker carrying
|
|
246
246
|
`session_state_snapshot`. `?snapshot=1` joins with a snapshot frame instead of a replay;
|
|
247
247
|
`?client_id=` names whose stream it is (`Bridge#open_streams_except`, used by `POST /exit`).
|
|
248
|
+
Each event goes out as clients get it (`Bridge::ClientEvent.project`, in `SSEWriter#write_event`, live
|
|
249
|
+
and replayed alike); only a `generation_chunk` changes, to chi's own format (the chunk contract below).
|
|
250
|
+
`?raw=1` writes events as the engine emitted them, for debugging on the worker's port: `chi web`'s
|
|
251
|
+
`/api/sessions/:id/stream` never passes it on (`Web::App#stream_query`).
|
|
248
252
|
An attached terminal's stream (`EventStream` with `rediscover:`) looks for the session's live
|
|
249
253
|
worker whenever it drops: a new one (a restart) is followed from its snapshot, and the terminal
|
|
250
254
|
sends its requests there (`worker_changed`); it gives up after ~12 s, or at once when the session
|
|
251
255
|
was stopped.
|
|
256
|
+
|
|
257
|
+
The wire chunk (the contract; the engine's own event keeps the provider's `payload` and the raw `content`):
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
event: generation_chunk
|
|
261
|
+
data: {"type":"generation_chunk","iteration":2,"text":"…","thinking":"",
|
|
262
|
+
"tool_call":true, // only when set
|
|
263
|
+
"counts":{"prompt_tokens":489,"completion_tokens":12,
|
|
264
|
+
"total_tokens":501,"context_window_tokens":32768}, // only when the server reported counts
|
|
265
|
+
"event_seq":812}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`counts` is `ContextUsage.normalize` of the provider's payload without `ctx_pct` (normalize computes it
|
|
269
|
+
without the session's LLM context budget; a client counts `total_tokens` against the window or budget, as
|
|
270
|
+
`ContextStatus#generation_done` does for the TUI) and with its nil keys left out: cumulative for the
|
|
271
|
+
request, a missing key means unknown (never 0), `prompt_tokens` includes cached tokens, and
|
|
272
|
+
`context_window_tokens` is there only when the payload reported a window itself (llama.cpp's `/completion`
|
|
273
|
+
doesn't: the client uses `generation_started`'s). Unknown provider fields never cross; a field is added here
|
|
274
|
+
when a client needs it, never as a catch-all bag. Details: [internals/context-telemetry.md](internals/context-telemetry.md).
|
|
252
275
|
- `POST /session/:id/turn` — fire-and-forget turn creation; returns `202` with an `enqueued_id`
|
|
253
276
|
(delivery is at-least-once via the worker's file-IPC input path — it never calls `run_turn`
|
|
254
277
|
across the HTTP boundary). Only the bridge's own session: another id, in the path or the body's
|
|
@@ -270,8 +293,12 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
|
|
|
270
293
|
arrives as `:recap_ready`. Answers mid-turn too.
|
|
271
294
|
- `POST /session/:id/answer` — answer the pending question; `200`, `409` when another client
|
|
272
295
|
answered first or it is gone, `400` for an invalid selection. With `client_id: "cli:answer"`
|
|
273
|
-
(`chi answer`, a parent agent)
|
|
274
|
-
`
|
|
296
|
+
(`chi answer`, a parent agent) the answer is checked by `Guardrails::ParentPolicy` in
|
|
297
|
+
`QuestionDesk#answer`: the question carries each option's effect (`effects`, set by its asker:
|
|
298
|
+
`Approval.payload`, `ContinueOffer`, `Relay.card`; derived for an untagged one) and `user_only`,
|
|
299
|
+
and an option beyond the worker's `guardrails.parent_answers` (tightened by the session's own
|
|
300
|
+
`parent_answers`: `ParentPolicy.for_session`) is `403 parent_answer_refused` (with its `reason`),
|
|
301
|
+
the question still open. `chi answer` makes the same check first, from the session file.
|
|
275
302
|
- `POST /session/:id/question/dismiss` — leave the question unanswered (an approval: denied);
|
|
276
303
|
`200`, or `409` when it is no longer pending.
|
|
277
304
|
- `POST /session/:id/tasks/stop` — `{task_id}`: stop a background task as the user
|
|
@@ -295,7 +322,8 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
|
|
|
295
322
|
pending question's `relayed_to` mark (`QuestionDesk#annotate`, `:question_relay`); `answered`
|
|
296
323
|
makes the child ask its parent's Bridge for the answer (`RelayVerifier`) and take it only for
|
|
297
324
|
this child and the question pending now. `200`, `409` no longer pending, `403` a parent agent's
|
|
298
|
-
|
|
325
|
+
pick beyond the child's own `guardrails.parent_answers` and session `parent_answers` (`parent_answer_refused`; the
|
|
326
|
+
relay card in the parent was held to the parent's), `422 relay_unverified` (the question
|
|
299
327
|
stays open).
|
|
300
328
|
- `POST /session/:id/relay/status` — on a parent's worker, `{relay_id}`: what its `RelayDesk`
|
|
301
329
|
(memory only) holds for that relay (`child_id`, `child_question_id`, `state`, `answer`, `by`);
|
|
@@ -369,7 +397,8 @@ The page's frequent reads of `GET /api/sessions/:id` stay light too:
|
|
|
369
397
|
The page binds an answer's `display_refs` to its bubble (`answer_refs.js`: on a reload's render and on this
|
|
370
398
|
read's re-render) and pairs them with the bubble's links and text before the `path:line` scan. A file ref outside the
|
|
371
399
|
session's folder is dropped, as a reload drops it: the folder `/tail` names, else (an older worker) the hub's
|
|
372
|
-
session list entry's
|
|
400
|
+
session list entry's, else (refs to check, no hub entry) the session file's; with no folder known at all, every
|
|
401
|
+
file ref is dropped rather than shown unchecked.
|
|
373
402
|
A plugin tool's row refs (`refs`, `RowRefs`) ride its tool events and, on a reload, its part (saved as
|
|
374
403
|
`tool_refs`); `turn_view.js` binds them to the row's title (`refs.js bindRowRefs`: the first on the title,
|
|
375
404
|
the others in its menu).
|
|
@@ -446,7 +475,9 @@ both go through `ToolRunner` (events, hooks, veto, output cap) and
|
|
|
446
475
|
chat `tool_calls`) are built by one table, `Tools::BuiltinCalls`, from the
|
|
447
476
|
parsed `{name, args}` (derived from `TOOL_SCHEMAS`), so a call looks the same to
|
|
448
477
|
hooks and tools whichever model made it. Both loops' `generation_chunk` carries
|
|
449
|
-
`thinking:`, `text:` and `
|
|
478
|
+
`thinking:`, `text:`, `content:` and the provider's `payload:` (the Bridge projects it for its
|
|
479
|
+
clients: `text`, `thinking`, `tool_call` and chi's `counts`, no `payload` or `content`; see the
|
|
480
|
+
Bridge's stream above): the chat loop's thinking is the
|
|
450
481
|
server's `reasoning_content`; the raw-prompt loop splits each generation's
|
|
451
482
|
stream with a fresh `ThoughtStreamSplitter` (Qwen's `<think>` and Gemma's
|
|
452
483
|
`<|channel>thought … <channel|>` are thinking, tool-call bodies are dropped
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Writing a bundle
|
|
2
|
+
|
|
3
|
+
Read this first when someone asks for new chi functionality "as a bundle". It is the short path: the loop, the
|
|
4
|
+
files, what a hook sees, how to test, and what goes wrong. The full references are in this same `docs/` folder:
|
|
5
|
+
[plugins.md](plugins.md) (the `chi.*` API and `ctx`) and [hooks.md](hooks.md) (every event's fields).
|
|
6
|
+
|
|
7
|
+
## The loop
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
chi bundle new my-bundle # ./my-bundle: manifest.yml, plugin.rb, test/my_bundle_test.rb, README.md
|
|
11
|
+
cd my-bundle # edit plugin.rb and the test
|
|
12
|
+
chi bundle test # the tests, on chi's own Ruby and lib (exit status: the tests')
|
|
13
|
+
chi bundle check --fix # manifest, files, shas (--fix rewrites them), requires_chi, install + load in a sandbox
|
|
14
|
+
chi bundle install . # into the user's chi; a NEW session loads it (a running one doesn't)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`check` prints one finding per line, `error|warn|info CODE file[:line]: message (fix: hint)`, and exits 1 on any
|
|
18
|
+
error. Run it after every edit: an install takes exactly what it checks. It installs into a temp sandbox, not
|
|
19
|
+
the user's config or state, but `check` and `test` run the bundle's plugin and hooks in their own process, as
|
|
20
|
+
written: run them on code you'd install.
|
|
21
|
+
|
|
22
|
+
## The files
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
# manifest.yml
|
|
26
|
+
name: my-bundle # a plain name; chi bundle install takes it from here
|
|
27
|
+
version: 0.1.0
|
|
28
|
+
scope: system
|
|
29
|
+
description: "What it adds, in one line"
|
|
30
|
+
requires_chi: ">= 0.85.0" # the chi it needs (new writes the running one; check warns when a call needs newer)
|
|
31
|
+
plugin:
|
|
32
|
+
file: plugin.rb
|
|
33
|
+
sha256: sha256:<hex> # `chi bundle check --fix` fills it; never compute it by hand
|
|
34
|
+
# files: # memories it installs (top-level *.md); without files: every *.md but README & co.
|
|
35
|
+
# my_notes.md: sha256:<hex>
|
|
36
|
+
# hooks: # hooks/*.rb files (a plugin's chi.on is usually simpler)
|
|
37
|
+
# needs: [gh] # outside commands it relies on
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`plugin.rb` defines `class Plugin` with `register(chi)`; `initialize(settings)` (optional) gets config.yml
|
|
41
|
+
`bundles: my-bundle:` as a Hash with string keys, once, when the plugin loads. A value a session may set
|
|
42
|
+
(`chi --set my-bundle.KEY=V`, listed in the manifest's `session_settings:`) is read from `ctx.settings` at call
|
|
43
|
+
time. `chi bundle check --set KEY=VALUE` gives the sandboxed load a setting.
|
|
44
|
+
|
|
45
|
+
## What a hook sees
|
|
46
|
+
|
|
47
|
+
`chi.on(event) { |event, ctx| … }` gets a Hash. The events, in a turn's order: `session_start`, `before_turn`,
|
|
48
|
+
`before_generation`, `generation_progress`, `after_generation`, `before_tool_call`, `after_tool_call`,
|
|
49
|
+
`after_turn`, `session_end` (the fields of each: [hooks.md, Hook Events](hooks.md#hook-events)). A symbol not in
|
|
50
|
+
that list is never fired (`check` warns). The two a bundle wants most, for an `execute`:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# before_tool_call: vote on the call before it runs
|
|
54
|
+
{ type: :before_tool_call, iteration: 1,
|
|
55
|
+
call: { name: "execute", content: "rm -rf build" }, params: "execute rm -rf build",
|
|
56
|
+
guardrail: verdict, # event[:guardrail].deny!("why") / .ask!("why"); ctx.stop_turn denies and stops the turn
|
|
57
|
+
context: { cwd: "/work", repo_root: nil, branch: nil, session_id: "…", interface: :worker, origin: nil },
|
|
58
|
+
targets: { command: "rm -rf build", paths: [], cwd: "/work", repo_root: nil, outside_repo: false, git_dirs: [] },
|
|
59
|
+
blocked: false, block_reason: nil }
|
|
60
|
+
|
|
61
|
+
# after_tool_call: what it did
|
|
62
|
+
{ type: :after_tool_call, iteration: 1, tool: "execute", status: "error", # "ok" | "error" | "blocked" | "stopped"
|
|
63
|
+
call: { name: "execute", content: "make test" }, elapsed_ms: 812,
|
|
64
|
+
result: "stdout:\nok\n\nstderr:\n1 failed\n\nexit: 1", # the tool's own text, never cut
|
|
65
|
+
output: "[execute]\nstdout:\n…" } # what the model reads: may be cut
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Match on `result:`, not `output:`. An execute's result is its `stdout:` and `stderr:` blocks (each left out when
|
|
69
|
+
empty), then `exit: N` (`exit: 0 (no output)` when both were empty); a non-zero exit makes `status:` `"error"`.
|
|
70
|
+
Act through `ctx`: `ctx.notify(text, level: :warn)`, `ctx.steer(text)` (a line the model reads before its next
|
|
71
|
+
step), `ctx.card(…)`, `ctx.stop_turn(reason)`, `ctx.ask_user(…)`, `ctx.data_dir` (the bundle's own folder),
|
|
72
|
+
`ctx.cwd`, `ctx.log`. In a test, each recorded notice, steer or stop names its hook as `"plugin.rb (bundle
|
|
73
|
+
my-bundle)"`, the plugin's label (a `hooks/x.rb` file's is `"x.rb (bundle my-bundle)"`).
|
|
74
|
+
|
|
75
|
+
## Testing it
|
|
76
|
+
|
|
77
|
+
`chi bundle new` writes a Minitest test that passes as written: copy its shape. The kit (`samagotchi/testing`)
|
|
78
|
+
loads the bundle's source through chi's own loaders, in a sandbox (temp HOME, config and state), and records what
|
|
79
|
+
it did at chi's edges:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
bundle = load_bundle(BUNDLE, settings: { "greeting" => "hi" }) # in a Samagotchi::Testing::Case test
|
|
83
|
+
bundle.command("/my-bundle chi") # → what the command returned
|
|
84
|
+
bundle.fire(:after_tool_call, tool: "execute", status: "error", result: "exit: 1 (no output)", call: { … })
|
|
85
|
+
bundle.tool_call({ name: "execute", content: "rm -rf /" }).deny? # before_tool_call hooks, through the real gate
|
|
86
|
+
bundle.tool("my_tool", path: "a.txt") # a chi.tool, as the model calls it
|
|
87
|
+
bundle.notices, bundle.steers, bundle.cards, bundle.stops, bundle.questions # what it did
|
|
88
|
+
bundle.answer({ selected: "yes" }); bundle.model_answers << "text" # ask_user / ask_model answers
|
|
89
|
+
bundle.http.stub(:get, "https://api.example/x", json: { ok: true }) # ctx.http, never the network
|
|
90
|
+
bundle.set(cwd: dir, running: false); sandbox.config(hash); sandbox.fake_executable("gh", "echo '{}'")
|
|
91
|
+
bundle.install # installed as chi installs it: warnings, load failures
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The full list is in [plugins.md, Testing a bundle](plugins.md#testing-a-bundle-experimental). The kit is
|
|
95
|
+
experimental (it may change before chi 1.0). If a test needs a fake of chi's own (a stub of a `Samagotchi::`
|
|
96
|
+
class, a hand-built `ctx`), that is a gap in the kit: say so to the user rather than reaching into chi.
|
|
97
|
+
|
|
98
|
+
## Pitfalls
|
|
99
|
+
|
|
100
|
+
- **Shas.** Every file the manifest names has a sha256 line, and an edit makes it stale: the install then warns
|
|
101
|
+
of a checksum mismatch, and a line with no sha installs unchecked. `chi bundle check --fix` rewrites them; run it
|
|
102
|
+
after the last edit.
|
|
103
|
+
- **requires_chi.** Use the chi the user runs (`chi --version`). `check` warns when the plugin calls something
|
|
104
|
+
newer than the floor (`ctx.http` needs 0.80.0, `ctx.cost` 0.83.0).
|
|
105
|
+
- **trust_level.** Leave it out. `reviewed` / `trusted` are for someone who reviewed the bundle, not its author.
|
|
106
|
+
- **Restart.** A session loads bundles when it starts: after an install or a settings change, the user starts a
|
|
107
|
+
new session (or `chi sessions restart ID`).
|
|
108
|
+
- **chi's internals.** A bundle may use `chi.*`, `ctx` and the names
|
|
109
|
+
[plugins.md, Stable for bundles](plugins.md#stable-for-bundles) lists; any other `Samagotchi::` name may change
|
|
110
|
+
in any release (`check` warns: `boundary`).
|
|
111
|
+
- **Load errors.** A plugin that raises at load is skipped and the session goes on: `check` shows the error at its
|
|
112
|
+
`plugin.rb:LINE`.
|
|
113
|
+
|
|
114
|
+
## Verifying it live
|
|
115
|
+
|
|
116
|
+
After `chi bundle install .`, one real session shows it works with the user's chi:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
chi send --new --wait --format json --dir /tmp/try -m "/my-bundle chi" # the JSON names the session id
|
|
120
|
+
grep plugin_loaded ~/.local/state/samagotchi/samagotchi.log | tail -1 # bundle=my-bundle commands=1 hooks=1
|
|
121
|
+
chi sessions delete --force SESSION_ID # leave no test session behind
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
(`$XDG_STATE_HOME/samagotchi/samagotchi.log` when `XDG_STATE_HOME` is set.) Then tell the user what it adds,
|
|
125
|
+
how to use it, and that a new session picks it up.
|
data/docs/cli.md
CHANGED
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
- `chi note [--source NAME] [-m TEXT] (ID|PREFIX)... | --all` — add a context note (TEXT or stdin) to sessions: background the model sees on its next turn; it starts no turn (see [Sessions: Context notes](sessions.md#context-notes))
|
|
20
20
|
- `chi context <add|push|ls|show|refresh|rm|mute|unmute> …` — attach live external text to sessions (a command chi runs every so often, or text pushed in); the model gets a short note when it changes and reads it with `context_read`; `chi context add <URL>` attaches a GitHub PR with the github-pr bundle (see [Attached context](context.md))
|
|
21
21
|
- `chi send [-m TEXT] [--image PATH]... [--cut | --queue] (ID|PREFIX)...` — send a message to sessions as if typed there: a turn starts (or a running one takes it at its next step; `--cut` asks it to cut long thinking for it; `--queue` runs it as a turn of its own after the running one, and `--queue --wait` waits for that turn's own answer); piped stdin goes above `-m` as quoted context, and `--image` attaches images (see [Sessions: Sending a message](sessions.md#sending-a-message)); `--new` starts a session with it instead (`--new --continues ID|last:ID` the next link of that session's chain, see [Session chains](sessions.md#session-chains)), and `--wait` prints the answer (`--wait ID` with no message waits for the next reply without sending — to an idle session whose last turn already ended, that ended turn's reply or end comes back; `--wait --turn T ID` with no message waits for exactly turn T, whether running, queued or last ended; `--format json` includes its `turn_id` when known, for waiting again after exit 3 or 4; exit 3 a question waits, its options on stderr; exit 4 `--timeout` passed with the turn still running; see [Starting a session](sessions.md#starting-a-session))
|
|
22
|
-
- `chi answer ID --question QID (--option N|LABEL)... [--text T] [--timeout S] [--format json]` (or `--dismiss`) — answer the question a session waits on (the one `chi send --wait` exited 3 with), then wait and print what comes next as `chi send --wait` does: the reply (0), the next question (3), still running after `--timeout` (4). `--option` is 1-based or the label, repeated on a multi-select question; `--text` is free text, or a Deny's reason; `--dismiss` leaves it unanswered and the model finishes its reply. A question no longer open (the web answered first) isn't answered again: it waits for that turn's reply. An option the question doesn't offer exits 2; a worker that is gone exits 1 (`send the task again: chi send --wait -m "…" ID`).
|
|
22
|
+
- `chi answer ID --question QID (--option N|LABEL)... [--text T] [--timeout S] [--format json]` (or `--dismiss`) — answer the question a session waits on (the one `chi send --wait` exited 3 with), then wait and print what comes next as `chi send --wait` does: the reply (0), the next question (3), still running after `--timeout` (4). `--option` is 1-based or the label, repeated on a multi-select question; `--text` is free text, or a Deny's reason; `--dismiss` leaves it unanswered and the model finishes its reply. A question no longer open (the web answered first) isn't answered again: it waits for that turn's reply. An option the question doesn't offer exits 2; a worker that is gone exits 1 (`send the task again: chi send --wait -m "…" ID`). What a parent may pick follows `guardrails.parent_answers`, or the session's own `parent_answers` when stricter (`soft`, the default: a deny, a plain answer, Continue and "Allow once"; `standard`: no allow; `hard`: no Continue either), never a wider scope or an allow on chi's own config; a refused pick exits 1 with what to answer instead (a convention, not a security boundary; see [Guardrails](guardrails.md#answers-from-a-parent-agent))
|
|
23
23
|
- `chi desktop install|upgrade|uninstall|status` — the macOS "Send to chi" helper: a Service and a ⌃⌥⌘N hotkey that send text or images to a session (live, stopped or new) as a message (⏎, a turn runs) or a context note (⌘⏎) (see [Desktop helper](desktop.md))
|
|
24
24
|
- `chi models [--format text|json] [--timeout S] [TEXT]` — list the models every configured host offers, as the names `--model` takes (see [Listing the models](#listing-the-models))
|
|
25
25
|
- `chi self` — print version, source dir (checkout or installed gem), config/memory/session paths, model/host and bundles. Run by a session's `execute`, the `model` row (and host, loop, profile, thinking, served model) is **that session's** model, labelled `(this session <id8>; default …)`; elsewhere it is the default, `(default)`. `model key` is the memory overlay key (`<name>.<key>.md`), and `model notes` the model notes a prompt of that model loads (`model_notes_deepseek (system, 612 chars)`, `none`; inside a session its `--mute` list is left out). `chi self --model` prints only the model a new session starts on, an alias resolved to its target (the desktop helper's hint)
|
|
26
26
|
- `chi update [--dry-run] [--no-gem] [--no-bundles] [--no-desktop]` — update an installed chi: the gem, the system bundle, the shipped bundles you installed and the desktop helper, in one table (see [Updating](#updating))
|
|
27
|
-
- `chi bundle install|upgrade|uninstall|status|diff|list|build|trash` — manage memory bundles (see [Bundle hooks](hooks.md#bundle-hooks-unified-workflow-bundle)); `list` shows the installed ones and the ones shipped with chi, which `install <name>` installs; `core` and `dev` are profiles that install a set of them (see [Bundle profiles](memory.md#bundle-profiles-core-and-dev), [Guardrails](guardrails.md), [Plugins](plugins.md#the-btw-bundle), [the mcp bundle](plugins.md#the-mcp-bundle) [the loop-guard bundle](plugins.md#the-loop-guard-bundle), [the check-in bundle](plugins.md#the-check-in-bundle) and [the skills bundle](plugins.md#the-skills-bundle)). `trash` lists and empties the bundle trash (moved files from uninstalls/upgrades). A usage error (an unknown subcommand or flag, a missing argument, a bad `build --scope`) exits 2, as for the other subcommands. Plain `chi` and `chi web` exit 1 on an unknown flag, a flag missing its value or a stray argument
|
|
27
|
+
- `chi bundle install|upgrade|uninstall|status|diff|list|build|trash|new|test|check` — manage memory bundles (see [Bundle hooks](hooks.md#bundle-hooks-unified-workflow-bundle)); `list` shows the installed ones and the ones shipped with chi, which `install <name>` installs; `core` and `dev` are profiles that install a set of them (see [Bundle profiles](memory.md#bundle-profiles-core-and-dev), [Guardrails](guardrails.md), [Plugins](plugins.md#the-btw-bundle), [the mcp bundle](plugins.md#the-mcp-bundle) [the loop-guard bundle](plugins.md#the-loop-guard-bundle), [the check-in bundle](plugins.md#the-check-in-bundle) and [the skills bundle](plugins.md#the-skills-bundle)). `trash` lists and empties the bundle trash (moved files from uninstalls/upgrades). A usage error (an unknown subcommand or flag, a missing argument, a bad `build --scope`) exits 2, as for the other subcommands. Plain `chi` and `chi web` exit 1 on an unknown flag, a flag missing its value or a stray argument. `new`, `test` and `check` are for writing one: see [Writing a bundle](#writing-a-bundle-new-test-check)
|
|
28
28
|
|
|
29
29
|
### First setup
|
|
30
30
|
|
|
@@ -87,6 +87,39 @@ chi bootstrap # try localhost 8080, 11434, 1234, 8000
|
|
|
87
87
|
A bundle that fails to install makes the exit 1. `--dry-run` says what it
|
|
88
88
|
would install.
|
|
89
89
|
|
|
90
|
+
### Writing a bundle: new, test, check
|
|
91
|
+
|
|
92
|
+
The loop for a bundle you're writing (the whole path: [bundle-authoring.md](bundle-authoring.md)):
|
|
93
|
+
|
|
94
|
+
- `chi bundle new NAME [--dir DIR]` writes `DIR/NAME/` (default `./NAME`; refused when it exists; `NAME` is
|
|
95
|
+
`a-z` first, then `a-z`, `0-9`, `-`, `_`, as a slash command): `manifest.yml` (a block-form `plugin:` entry with
|
|
96
|
+
its sha filled, `requires_chi: ">= <this chi>"`), `plugin.rb` (a `/NAME` command and an `after_tool_call` hook),
|
|
97
|
+
`test/NAME_test.rb` (Minitest on the bundle test kit, passing as written) and `README.md`, then prints the next
|
|
98
|
+
steps. Exit 1 when it refuses, 2 for usage.
|
|
99
|
+
- `chi bundle test [PATH] [-- ARGS]` runs the bundle's tests on the Ruby and the lib chi runs on, outside any
|
|
100
|
+
Bundler env: `test/**/*_test.rb` on Minitest (`-r samagotchi/testing/minitest`), or else `spec/` on RSpec with
|
|
101
|
+
the kit (`--options /dev/null -r samagotchi/testing/rspec`, as `rake bundles:specs`). `ARGS` go to the runner
|
|
102
|
+
(`-- -n /name/`). Exit status: the tests'; 1 with no tests; 2 for usage, or when minitest / rspec doesn't load on
|
|
103
|
+
chi's Ruby. The tests run the bundle's code as written, with your environment (the kit sandboxes each
|
|
104
|
+
test's HOME and XDG dirs, not the process).
|
|
105
|
+
- `chi bundle check [PATH] [--fix] [--set KEY=VALUE]` (PATH defaults to `.`) checks it as an install takes it, one
|
|
106
|
+
finding per line, `error|warn|info CODE file[:line]: message (fix: hint)`, then `ok|failed: N errors, N
|
|
107
|
+
warnings`. Exit 1 on any error, 0 otherwise, 2 for usage. It installs and loads the bundle in a temp sandbox
|
|
108
|
+
(HOME, config and state), not in yours, but in chi's own process: the plugin's and hooks' code runs as written,
|
|
109
|
+
so run it (and `test`) on code you'd install. The codes, in order: `manifest` (a parse error, verbatim; the run
|
|
110
|
+
stops), `missing-file`, `unlisted` (warn: a file an install leaves out, by `MemoryFiles`' rules, so a README
|
|
111
|
+
isn't one), `sha` (a stale or placeholder sha, or a plugin / hook / script entry with no sha line; `--fix`
|
|
112
|
+
rewrites the first two and prints `fixed sha FILE → sha256:…`), `requires-chi` (this chi doesn't satisfy it:
|
|
113
|
+
error; missing on a bundle with code: warn), `requires-chi-floor` (warn: the plugin calls an API newer than the
|
|
114
|
+
oldest chi `requires_chi` admits, from `Plugin::Api::SINCE`), `trust` (warn: `trust_level: reviewed|trusted` on a
|
|
115
|
+
bundle chi doesn't ship), `clash` (warn: a name chi ships, or one installed from elsewhere; the sandboxed install
|
|
116
|
+
then forces), `install` (each Installer warning, a `needs` one as warn, and each plugin or hook load failure at
|
|
117
|
+
the source `file:line`; skipped while a placeholder sha is unfilled), `registered` (info: each command, tool,
|
|
118
|
+
hook with its priority, init, toolkit, tool source and peer source), `hook-event` (warn: a `chi.on` event chi
|
|
119
|
+
never fires), `boundary` (warn: a `Samagotchi::` name outside "Stable for bundles"; a shipped bundle's listed
|
|
120
|
+
exceptions aside) and `tests` (info: how to run them, or "no tests"). `--set KEY=VALUE` (repeatable, typed as
|
|
121
|
+
`chi --set`) goes to `bundles: <name>:` in the sandbox's config.yml.
|
|
122
|
+
|
|
90
123
|
### Listing the models
|
|
91
124
|
|
|
92
125
|
`chi models` asks every host in `hosts:` for its models, in parallel, and prints the names `--model` (and
|
|
@@ -204,6 +237,7 @@ controls exit behavior (`--non-interactive`); `--resume` composes with both.
|
|
|
204
237
|
| `--model NAME` | Use this model for the run (overrides the configured default and a resumed session's model). |
|
|
205
238
|
| `--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. |
|
|
206
239
|
| `--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). |
|
|
240
|
+
| `--parent-answers LEVEL` | A new session's own `guardrails.parent_answers` (`hard`, `standard` or `soft`): what a parent agent may answer on its questions. It may only tighten config.yml's: the stricter of the two applies, and a looser one is saved as given with a warning (`--parent-answers soft is looser than config.yml's guardrails.parent_answers: standard; standard applies`). Saved in the session; a respawned worker, a fork and a continue keep it, a `delegate` child takes its parent's. Ignored with `--attach`/`--resume` (one line, as `--toolkit`); refused with `chi web`. Only you loosen it: `/parent-answers` in the terminal or the web. See [Guardrails](guardrails.md#answers-from-a-parent-agent). |
|
|
207
241
|
| `--set BUNDLE.KEY=VALUE` | A bundle setting for a new session (repeatable; a later one of the same key wins), laid over config.yml's `bundles: <bundle>:` in what the bundle's plugin reads (`ctx.settings`): `--set cost-cap.cap=3`. The value is typed simply: an integer (`3`, `-1`) or a decimal (`2.5`) is a number, `true` is true, `false` and `off` are false, anything else stays the text as given (`010`, `0x10`, `3:30`, `No`, `null`). For cost-cap, `0`, a negative number or `off` is no cap. Only a key the installed bundle's manifest lists in `session_settings:` is taken: a bundle that isn't installed (or whose plugin doesn't load), a key it doesn't list (a record installed before the bundle listed it says to run `chi update` or `chi bundle upgrade NAME`), or another shape (`--set foo`, `--set a.b.c=1`) is an error naming what it takes, exit 2, nothing started. Saved in the session; a respawned worker, a fork and a continue keep it, a delegate child doesn't. Ignored with `--attach`/`--resume` (one line, as `--toolkit`); refused with `chi web`. See [Per-session settings](plugins.md#per-session-settings). |
|
|
208
242
|
| `--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. |
|
|
209
243
|
| `--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. |
|
|
@@ -565,6 +599,18 @@ running turn keeps its tools. `/toolkit` alone answers mid-turn too; a change
|
|
|
565
599
|
sent while a turn runs waits for the turn's end. The web shows a session that
|
|
566
600
|
isn't on `full` as the `tools` chip. See [Toolkits](configuration.md#toolkits).
|
|
567
601
|
|
|
602
|
+
`/parent-answers` shows what a parent agent may answer on this session's
|
|
603
|
+
questions: the level that applies, the session's own and config.yml's
|
|
604
|
+
(`parent answers: standard (this session: standard; config.yml
|
|
605
|
+
guardrails.parent_answers: soft)`). `/parent-answers hard|standard|soft` sets the
|
|
606
|
+
session's own, `/parent-answers default` drops it; it is saved at once and
|
|
607
|
+
holds from the next answer. It never goes looser than config.yml's (the reply
|
|
608
|
+
says when config.yml's applies instead). Anyone may tighten it; only you loosen
|
|
609
|
+
it, at the REPL, an attached terminal or the web: `chi send`, a piped or marked
|
|
610
|
+
attach and `chi -p --non-interactive` are refused. `/parent-answers` alone answers
|
|
611
|
+
mid-turn too; a change waits for the turn's end. No chip: its reply is where the
|
|
612
|
+
web shows it. See [Guardrails](guardrails.md#answers-from-a-parent-agent).
|
|
613
|
+
|
|
568
614
|
A `/thinking`, `/reasoning-history` or `/toolkit` change that restarts the prompt cache
|
|
569
615
|
asks first: the reply says what it costs, changes nothing, and names the line
|
|
570
616
|
that applies it (`/thinking low --yes`). `--yes` (or `-y`) applies it at once.
|
|
@@ -1153,7 +1199,9 @@ Behavior notes:
|
|
|
1153
1199
|
- Stop keeps the interrupted turn's work in the conversation, as a Ctrl-C or a new prompt does, with a note for the
|
|
1154
1200
|
model that it wasn't continued (or your reason); `!rollback` still erases it.
|
|
1155
1201
|
- `--non-interactive` (`chi -p`) has nobody to ask: the turn ends there. A `chi send --wait` on a session nobody can
|
|
1156
|
-
answer (an older worker) reports `limit` (exit 1) with how to continue it: `chi send ID -m '/continue yes'
|
|
1202
|
+
answer (an older worker) reports `limit` (exit 1) with how to continue it: `chi send ID -m '/continue yes'`
|
|
1203
|
+
(not under `guardrails.parent_answers: hard`, config.yml's or the session's own: a parent agent's `/continue yes`, sent with `chi send` or typed into a
|
|
1204
|
+
piped `chi --attach`, is held to it as `chi answer --option Continue` is, and refused there).
|
|
1157
1205
|
- The check-in bundle's card closes as the turn ends, before the step-limit question opens, so the two never stand
|
|
1158
1206
|
together; with check-in's default `after: 50` and the limit of 300 a long turn shows check-in cards (at 50, 100, …)
|
|
1159
1207
|
well before it.
|
data/docs/configuration.md
CHANGED
|
@@ -131,7 +131,8 @@ The config reader checks the file's modification time and size whenever a
|
|
|
131
131
|
setting is read. That makes a changed value available to a live reader; it
|
|
132
132
|
does not reload every object that already holds a snapshot. "Worker start"
|
|
133
133
|
includes a worker respawn. A running turn keeps the settings it already
|
|
134
|
-
resolved.
|
|
134
|
+
resolved. chi tells you when a changed setting applies only after a worker
|
|
135
|
+
or `chi web` restart.
|
|
135
136
|
|
|
136
137
|
| Key | Applies | Note |
|
|
137
138
|
|---|---|---|
|
|
@@ -144,6 +145,7 @@ resolved.
|
|
|
144
145
|
| `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. |
|
|
145
146
|
| Other `models.<key>.*` settings | Next turn or model lookup | Sampling, thinking, vision, context, window, price and steer settings are resolved from the file for their next use. |
|
|
146
147
|
| `thinking.level` | Next turn start | A session's own `/thinking` level is saved in that session and takes precedence; a running turn keeps its level. |
|
|
148
|
+
| `turn.max_iterations` | Next turn start | The configured step limit is resolved when each turn begins. |
|
|
147
149
|
| `cache.*` | Next request or turn-end warm-up | Cache key and TTL are read for a request; warm-up is checked after a turn. |
|
|
148
150
|
| `retry.max`, `retry.base_delay`, `retry.max_delay` | Worker start | The client's network retry policy is built with its HTTP client. |
|
|
149
151
|
| `retry.empty_answer` | Next empty-answer retry | It is checked when a turn needs to retry an empty answer. |
|
|
@@ -154,12 +156,12 @@ resolved.
|
|
|
154
156
|
| `thinking.turn_preamble`, `skip_agents_md` | Prompt rebuild | A running engine caches its built prompt; a new worker or `/model` rebuild uses the changed value. |
|
|
155
157
|
| `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. |
|
|
156
158
|
| `context.every_seconds` | Next context poll | The poller checks it for each source that has no explicit interval. |
|
|
157
|
-
| `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. |
|
|
159
|
+
| `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. |
|
|
158
160
|
| `hooks`, `bundles` | Worker start | Hooks, plugins and their bundle settings load with the engine. |
|
|
159
161
|
| `recap.*` | Worker start | The engine builds its recap job once. |
|
|
160
162
|
| `session.shared` | Next `chi` invocation | It selects attached-worker or in-process mode for that invocation. |
|
|
161
163
|
| `session.idle_exit_minutes`, `session.keep_empty`, `session.max_children`, `session.delegate_reports`, `session.max_wakes` | Worker start or next matching lifecycle decision | Existing workers keep their idle-exit timer; the other values are read for the next corresponding decision. |
|
|
162
|
-
| `session.retention_
|
|
164
|
+
| `session.retention_*`, `session.max_count`, `session.keep_status`, `session.sweep_interval_hours` | Next retention sweep | `chi web` sweeps on its configured schedule and on `GET /api/sessions`; `chi sessions prune` uses the values for that invocation. The sweep interval is checked on the next hub tick. |
|
|
163
165
|
| `web.*` | `chi web` start | Restart `chi web` to change its bind address, port or page settings. |
|
|
164
166
|
| `log.*` | Process start | Each process holds its configured log sink and level after first use; restart that process. |
|
|
165
167
|
| `history.file` | Next history access | The TUI and web composer resolve its path when they read or append history. |
|
|
@@ -1631,7 +1633,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
1631
1633
|
| `guardrails.enabled` | `true` | | `false`: no rules, and hooks' asks are dropped (a deny still applies). config.yml only: a worker unsets any `SAMAGOTCHI_GUARDRAILS_*` it inherits. See [Guardrails](guardrails.md). |
|
|
1632
1634
|
| `guardrails.mode` | `auto` | | Which rules vote: `auto` leaves out the rules tagged `modes: [strict]` (in the guardrails bundle: `git rebase`, writes and git outside the session's repo); `strict` runs them all. An unknown value warns and is `auto`. config.yml only. See [Guardrails](guardrails.md#modes). |
|
|
1633
1635
|
| `guardrails.small_models` | `auto` | | Which models get the `models: small` rules: `auto` (32B or less by the size in the name, an MoE's active size), a list of globs, or `[]`. config.yml only. See [Guardrails](guardrails.md#rules-for-some-models). |
|
|
1634
|
-
| `guardrails.
|
|
1636
|
+
| `guardrails.parent_answers` | `soft` | | Which options a parent agent (`chi answer`) may pick on a session's question, by their effect: `hard` (a deny, a Stop, a plain answer), `standard` (+ Continue and other spending options), `soft` (+ "Allow once"); never a wider scope or an allow on chi's own config. An invalid value warns and is `hard`. A session's own `parent_answers` (`chi --parent-answers`, `/parent-answers`) may only tighten it. Replaces `guardrails.parent_approvals` and `turn.parent_continue`. config.yml only: no environment variable (a parent can still pick the config dir with `XDG_CONFIG_HOME`). A convention for an honest parent, not a security boundary. See [Guardrails](guardrails.md#answers-from-a-parent-agent). |
|
|
1635
1637
|
| `log.file` | state dir | yes | See "Debug Log File". |
|
|
1636
1638
|
| `log.disable` | `false` | yes | No file logging. |
|
|
1637
1639
|
| `log.level` | `info` | yes | `debug`, `info`, `warn`, `error`. |
|
|
@@ -1675,7 +1677,6 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
|
|
|
1675
1677
|
| `steer.cut_after` | `20` | | Seconds a generation must have streamed only thinking before a message sent as a cut (`/cut TEXT`, `chi send --cut`) cuts it; other messages go in at the next step; `0` = never. `models.<key>.steer_cut_after` and `hosts.<name>.steer_cut_after` come first; see [Messages during a running turn](#messages-during-a-running-turn-steer-and-cut). |
|
|
1676
1678
|
| `models.<key>.steer_cut_after`, `hosts.<name>.steer_cut_after` | none | | A model's or host's seconds a cut waits for its thinking: whole seconds, `0` = never (e.g. a slow local thinker). `/model` shows the resolved one. See [Messages during a running turn](#messages-during-a-running-turn-steer-and-cut). |
|
|
1677
1679
|
| `turn.max_iterations` | `300` | | A turn's step limit: model ↔ tool rounds before it stops and asks to continue (an integer ≥ 1). `--no-interrupt` turns get the larger of 1000 and this. See [CLI: Iteration Limit Behavior](cli.md#iteration-limit-behavior). |
|
|
1678
|
-
| `turn.parent_continue` | `true` | | Whether a parent agent (`chi answer`) may answer Continue to a session's step-limit question; `false`: Stop only. config.yml only: no environment variable. See [Sub-agent](sub-agent.md) and [Guardrails](guardrails.md#approvals-from-a-parent-agent). |
|
|
1679
1680
|
| `update.gem` | `true` | | `false`: `chi update` never installs a newer gem (`--no-gem` for one run). See [CLI: Updating](cli.md#updating). |
|
|
1680
1681
|
| `update.bundles` | `true` | | `false`: `chi update` leaves the shipped bundles to `chi bundle upgrade` (`--no-bundles`). |
|
|
1681
1682
|
| `update.desktop` | `true` | | `false`: `chi update` leaves the desktop helper alone (`--no-desktop`). |
|