samagotchi 0.81.0 → 0.83.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 +45 -1
- data/README.md +2 -2
- data/docs/architecture.md +3 -0
- data/docs/cli.md +1 -1
- data/docs/configuration.md +54 -10
- data/docs/hooks.md +1 -1
- data/docs/memory.md +1 -1
- data/docs/plugins.md +58 -1
- data/docs/releasing.md +1 -1
- data/docs/sessions.md +2 -1
- data/docs/sub-agent.md +23 -8
- data/lib/samagotchi/bundles/core/manifest.yml +4 -2
- data/lib/samagotchi/bundles/cost-cap/manifest.yml +11 -0
- data/lib/samagotchi/bundles/cost-cap/plugin.rb +228 -0
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/child_reports.rb +25 -8
- data/lib/samagotchi/cli/parent_wait.rb +9 -3
- data/lib/samagotchi/engine.rb +3 -2
- data/lib/samagotchi/guardrails/rm_targets.rb +4 -2
- data/lib/samagotchi/guardrails/shell_paths.rb +5 -3
- data/lib/samagotchi/idle_client.rb +2 -7
- data/lib/samagotchi/llm/http.rb +7 -2
- data/lib/samagotchi/parent_report.rb +7 -3
- data/lib/samagotchi/plugin/context.rb +8 -1
- data/lib/samagotchi/reply_wait.rb +66 -2
- data/lib/samagotchi/send_command.rb +47 -8
- data/lib/samagotchi/session_metrics/cost.rb +37 -0
- data/lib/samagotchi/session_metrics/side_totals.rb +16 -9
- data/lib/samagotchi/session_metrics.rb +12 -0
- data/lib/samagotchi/testing/boundary.rb +15 -0
- data/lib/samagotchi/testing/bundle.rb +35 -9
- data/lib/samagotchi/testing/host.rb +26 -1
- data/lib/samagotchi/testing/recorder.rb +4 -1
- data/lib/samagotchi/tools/delegate_cursor.rb +45 -7
- data/lib/samagotchi/tools/delegate_wait.rb +40 -6
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/public/app.js +3 -1
- data/lib/samagotchi/web/public/question_cards.js +10 -5
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b7fb93e6e4741b5380cfaeaa2a27d37dd326099f2d3cf568b659941e9c34930
|
|
4
|
+
data.tar.gz: bc53710bd1f4dab4d2d5d4e1eb23814f367fc47833b930fd6bd7ee3a1411423b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 882ac69395d70f9b27e8b9922b704c1b1818ad400eeb4551dac38c905df7822d17ac43450f71b5c5a0653b0af9b45470098981f9ee25e36dc2f45a1bb2a9eeee
|
|
7
|
+
data.tar.gz: 1e982daa8adf0881d87ea82256e96565cceb09ab4d6793325a87ce0214df3355a5798c3da75f9afab30126aba7bd5f7f9facf7dfd1f032e3c2cfd9a5e4aaeed4
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,48 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.83.0] - 2026-10-18
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Bundles: `ctx.cost` reads the session's spend so far (reported + estimated, side requests included).
|
|
16
|
+
- The cost-cap bundle (in the core profile): a per-session $ cap that pauses the turn with Continue / Stop, `/cap`
|
|
17
|
+
to see or set it; config `bundles: cost-cap: default/child/warn_at`. Existing installs get it with `chi update`
|
|
18
|
+
(core 0.3.2, + cost-cap).
|
|
19
|
+
- Docs: docs/configuration.md "When a change applies" says, per config.yml key, whether an edit applies on the
|
|
20
|
+
next turn, at worker start, at session creation or at `chi web` start.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- A delegate reply already taken as a report isn't returned again by `delegate_result` in the same turn.
|
|
25
|
+
- `chi send --wait ID` with no message recognizes a finished turn's reply across time zones.
|
|
26
|
+
|
|
27
|
+
## [0.82.0] - 2026-10-18
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- `chi send --wait --format json` names the turn it waited for (`turn_id`), and `chi send --wait --turn T ID` waits
|
|
32
|
+
for exactly that turn: running, queued or the last one that ended. A parent waiting again after exit 3 or 4 uses it
|
|
33
|
+
(docs/sub-agent.md).
|
|
34
|
+
- Bundles: the experimental test kit's `before_tool_call_event` builds the real guardrail event without firing
|
|
35
|
+
bundle hooks.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- `chi send --wait ID` with no message, on an idle session whose last turn already ended, returns that turn (its
|
|
40
|
+
reply, or its end when it left none) instead of timing out `running`.
|
|
41
|
+
- Web: a question or approval card arriving in the stage no longer snaps the stage to it; the stage eases it into
|
|
42
|
+
view.
|
|
43
|
+
- Guardrails: `rm` and protected-path checks treat a `$(cat <<'EOF' … EOF)` commit or PR message as data; a
|
|
44
|
+
substitution that runs, a piped message and a real command beside it are still checked.
|
|
45
|
+
- A late side request (an idle recap, `ctx.ask_model`) after the engine moved to another session no longer recreates
|
|
46
|
+
the deleted old session's folder.
|
|
47
|
+
- HTTP: failed-request messages in the log are one short UTF-8 line.
|
|
48
|
+
- Bundles, test kit: one `HookFailed` names every hook that failed; the boundary scan catches a `require_relative`
|
|
49
|
+
into chi's `lib/`; `before_tool_call_event` says when the guardrails didn't dispatch the event.
|
|
50
|
+
|
|
51
|
+
Update with `chi update` (the system bundle 0.82.0), then restart `chi web`.
|
|
52
|
+
|
|
11
53
|
## [0.81.0] - 2026-10-18
|
|
12
54
|
|
|
13
55
|
### Added
|
|
@@ -3338,7 +3380,9 @@ and long-lived sessions.
|
|
|
3338
3380
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
3339
3381
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
3340
3382
|
|
|
3341
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
3383
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.83.0...HEAD
|
|
3384
|
+
[0.83.0]: https://github.com/dm1try/samagotchi/compare/v0.82.0...v0.83.0
|
|
3385
|
+
[0.82.0]: https://github.com/dm1try/samagotchi/compare/v0.81.0...v0.82.0
|
|
3342
3386
|
[0.81.0]: https://github.com/dm1try/samagotchi/compare/v0.80.0...v0.81.0
|
|
3343
3387
|
[0.80.0]: https://github.com/dm1try/samagotchi/compare/v0.79.0...v0.80.0
|
|
3344
3388
|
[0.79.0]: https://github.com/dm1try/samagotchi/compare/v0.78.0...v0.79.0
|
data/README.md
CHANGED
|
@@ -78,8 +78,8 @@ Messages API, picks the
|
|
|
78
78
|
model (or asks, when there are several), sends one test request and writes
|
|
79
79
|
the config. With a config already there, it adds a `hosts:` entry and keeps
|
|
80
80
|
the rest of the file. Then it installs the `core` bundles (loop-guard,
|
|
81
|
-
check-in, guardrails, slow-commands, agents-md: the safety set for a
|
|
82
|
-
local model) and, on a terminal, offers `dev` (known-names, mcp, btw, skills,
|
|
81
|
+
check-in, guardrails, slow-commands, agents-md, cost-cap: the safety set for a
|
|
82
|
+
small local model, the cost cap inert until you set one) and, on a terminal, offers `dev` (known-names, mcp, btw, skills,
|
|
83
83
|
source-links). Edit the
|
|
84
84
|
file later as in [Configure](#configure); `chi bootstrap --help` has the
|
|
85
85
|
options.
|
data/docs/architecture.md
CHANGED
|
@@ -346,6 +346,9 @@ Side requests (the idle recap, a plugin's `ctx.ask_model` such as `/btw`) are co
|
|
|
346
346
|
`analytics.json` between turns (a running turn's end saves it). `tokens`, `model_segments` and the turn records
|
|
347
347
|
stay the turns' only. `SessionMetrics#persist` is serialized (its own mutex, taken before the metrics one), so two
|
|
348
348
|
threads' saves land in snapshot order.
|
|
349
|
+
A bundle's `ctx.cost` reads the same sums (`SessionMetrics#cost`, a frozen `SessionMetrics::Cost`): the `tokens`
|
|
350
|
+
block's `cost_sum` and `cost_estimate_sum` with the running turn's finished generations folded in, plus the `side`
|
|
351
|
+
block's, so it counts what earlier workers spent too ([plugins.md](plugins.md)).
|
|
349
352
|
|
|
350
353
|
The page's frequent reads of `GET /api/sessions/:id` stay light too:
|
|
351
354
|
- `?tail=1&recent=1` (each turn's end, a cancel, an answer display): `{tail, session: {id, status,
|
data/docs/cli.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
- `chi sessions list|stop|restart|archive|unarchive|delete|prune|clean|stats` — manage persisted sessions; `list` shows this git project's, `list --scope=all` every one, a delegated session with `↳ <parent>`, `list --archived` the archived ones too (see [Sessions](sessions.md)). `stats ID` prints a session's cost, tokens and progress (a live worker's `GET stats`, else its saved `analytics.json`; `--format json` one object) without running a model turn. A usage error (an unknown subcommand or flag, a flag missing its value, `stop`, `restart` or `delete` with no ids, a bad `list --format` or `--scope`, a `--days`, `--keep` or `--limit` that isn't a whole number) exits 2; an unknown or refused session exits 1
|
|
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
|
-
- `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; `--format json`
|
|
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
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`). An approval can be denied, not allowed, unless `guardrails.parent_approvals: once` (a convention, not a security boundary; see [Guardrails](guardrails.md#approvals-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))
|
data/docs/configuration.md
CHANGED
|
@@ -100,15 +100,15 @@ Behavior:
|
|
|
100
100
|
`config: unknown key 'default.modle' (did you mean 'default.model'?)`.
|
|
101
101
|
Names you choose under the maps below (host names, model ids) don't warn.
|
|
102
102
|
- Environment variables and CLI flags win over config-file values.
|
|
103
|
-
- An edit to the file needs no restart of `chi web
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
through its environment.
|
|
109
|
-
- A worker reads `hosts:` from the file
|
|
110
|
-
started later) sees a host added after its parent started. Hosts
|
|
111
|
-
variable sets them.
|
|
103
|
+
- An edit to the file needs no restart of `chi web`. A new worker reads the
|
|
104
|
+
file when it starts; a running worker sees only settings whose readers check
|
|
105
|
+
the file again (see [When a change applies](#when-a-change-applies)). The web
|
|
106
|
+
model picker refreshes `hosts:` when the file changes, but a running worker
|
|
107
|
+
keeps its host registry until it is restarted. A worker gets the CLI flags of
|
|
108
|
+
the chi that started it through its environment.
|
|
109
|
+
- A newly started worker reads `hosts:` from the file, so a delegate (or any
|
|
110
|
+
session started later) sees a host added after its parent started. Hosts
|
|
111
|
+
come only from config.yml: no environment variable sets them.
|
|
112
112
|
|
|
113
113
|
This lets you run `chi` without repeating common defaults such as model
|
|
114
114
|
and llama host/port on every invocation.
|
|
@@ -125,6 +125,49 @@ any per-run `--memory` values (config baseline first, deduped). A per-run
|
|
|
125
125
|
`--mute NAME` removes an entry from the merged list for that session (see
|
|
126
126
|
"Muting a memory" in cli.md).
|
|
127
127
|
|
|
128
|
+
## When a change applies
|
|
129
|
+
|
|
130
|
+
The config reader checks the file's modification time and size whenever a
|
|
131
|
+
setting is read. That makes a changed value available to a live reader; it
|
|
132
|
+
does not reload every object that already holds a snapshot. "Worker start"
|
|
133
|
+
includes a worker respawn. A running turn keeps the settings it already
|
|
134
|
+
resolved.
|
|
135
|
+
|
|
136
|
+
| Key | Applies | Note |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| `default.model` | New session | Existing sessions keep their saved model; change one with `/model`. |
|
|
139
|
+
| `default.input`, `no_default_input`, `no_interrupt` | Next CLI run | These set up that run's first prompt or turn limit. |
|
|
140
|
+
| `default.max_tokens`, `max_tool_output_chars`, `read.*`, `execute.*` except `execute.description`, `image.*` | Next matching request or tool use | An operation already in progress keeps its resolved limits. |
|
|
141
|
+
| `server.host`, `server.port`, `server.transport`, `hosts.*` | Worker start | Includes host addresses, APIs, declared models and host-level settings. The web model picker refreshes `hosts:` on file change; a running worker does not. |
|
|
142
|
+
| `server.open_timeout`, `server.read_timeout`, `server.first_token_timeout` | Worker start | The worker's clients retain the timeouts set when they were built. |
|
|
143
|
+
| `model_aliases` | Next model lookup | A session keeps its resolved model unless it is explicitly switched. |
|
|
144
|
+
| `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
|
+
| 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
|
+
| `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. |
|
|
147
|
+
| `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
|
+
| `retry.max`, `retry.base_delay`, `retry.max_delay` | Worker start | The client's network retry policy is built with its HTTP client. |
|
|
149
|
+
| `retry.empty_answer` | Next empty-answer retry | It is checked when a turn needs to retry an empty answer. |
|
|
150
|
+
| `memories` | Worker start | The running engine holds its preload list. The config list is not saved in the session file, so a respawn reads the current list. |
|
|
151
|
+
| `append_system_prompt` | Worker start for the file list | The config list is not saved in the session file. File text is read when the prompt is built; `/model` rebuilds it, while an already-built prompt is cached. CLI `--append-system-prompt` files are saved in the session. |
|
|
152
|
+
| `toolkit.default`, `toolkit.child` | Session creation | The chosen toolkit is saved; changing the defaults does not move existing sessions. |
|
|
153
|
+
| `toolkit.kits`, `execute.description`, `llm_context.policy` | Worker start | These are held by the engine's toolkit or tool registry. |
|
|
154
|
+
| `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
|
+
| `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
|
+
| `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. |
|
|
158
|
+
| `hooks`, `bundles` | Worker start | Hooks, plugins and their bundle settings load with the engine. |
|
|
159
|
+
| `recap.*` | Worker start | The engine builds its recap job once. |
|
|
160
|
+
| `session.shared` | Next `chi` invocation | It selects attached-worker or in-process mode for that invocation. |
|
|
161
|
+
| `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_*` | Next retention sweep | `chi web` sweeps on its configured schedule and on `GET /api/sessions`; `chi sessions prune` uses the values for that invocation. |
|
|
163
|
+
| `web.*` | `chi web` start | Restart `chi web` to change its bind address, port or page settings. |
|
|
164
|
+
| `log.*` | Process start | Each process holds its configured log sink and level after first use; restart that process. |
|
|
165
|
+
| `history.file` | Next history access | The TUI and web composer resolve its path when they read or append history. |
|
|
166
|
+
| `broadcast.*` | Next `chi broadcast` | Each broadcast invocation reads its targeting and triage settings. |
|
|
167
|
+
| `update.*` | Next `chi update` | The command reads which components to update when it runs. |
|
|
168
|
+
| `kitty.*`, `tmux.*` | `chi desktop upgrade` or desktop update | These settings are copied into the desktop helper's launch file. |
|
|
169
|
+
| `model.profile`, `reasoning_history` | Not from config.yml | These are CLI/environment settings; `models:` and `hosts:` provide their model- and host-specific forms. |
|
|
170
|
+
|
|
128
171
|
### Environment variables
|
|
129
172
|
|
|
130
173
|
Every setting has an environment variable (except the ones the table marks
|
|
@@ -575,7 +618,8 @@ hosts:
|
|
|
575
618
|
- A server that sends no cached counts has its whole prompt priced at `input`: an overestimate.
|
|
576
619
|
- Side requests (the idle recap, `/btw`, a plugin's `ctx.ask_model`) are priced by the same rule from the price of
|
|
577
620
|
the model they asked on its host: the recap's host and model (`recap.*`, else the session's), `ask_model(model:)`'s
|
|
578
|
-
host. They count on `/stats`' own `side requests:` line,
|
|
621
|
+
host. A recap using only `recap.base_url` has no host price. They count on `/stats`' own `side requests:` line,
|
|
622
|
+
never in the session's `cost:`.
|
|
579
623
|
|
|
580
624
|
## Thinking
|
|
581
625
|
|
data/docs/hooks.md
CHANGED
|
@@ -592,7 +592,7 @@ Notes:
|
|
|
592
592
|
- Ordering: bundle hooks fire by `(priority, bundle_name, hook_name)` (lower priority first), then plain `config.yml` hooks in registration order.
|
|
593
593
|
- Settings: a hook class with `initialize(settings = {})` gets the bundle's section of `config.yml` `bundles:` (see [Settings](#settings)).
|
|
594
594
|
- A bundle can also ship a `plugin.rb` whose `chi.on(event)` blocks are bundle hooks too, next to commands and tools; see [Plugins](plugins.md). There `ctx.notify`, `ctx.steer` and the other `ctx` helpers act as this page's `event[:notify]`… do for the event the block runs for.
|
|
595
|
-
- Shipped bundles (each in a profile: `core` or `dev`, see [Bundle profiles](memory.md#bundle-profiles-core-and-dev)): `chi bundle install guardrails` (rules, see [Guardrails](guardrails.md#the-guardrails-bundle)) and `chi bundle install known-names` (a hook, see [Guardrails](guardrails.md#the-known-names-bundle)), `chi bundle install source-links` (a hook: announces source refs, see [The source-links bundle](#the-source-links-bundle)), `chi bundle install btw` (a plugin: `/btw`, see [Plugins](plugins.md#the-btw-bundle)), `chi bundle install mcp` (a plugin: tools from MCP servers, see [Plugins](plugins.md#the-mcp-bundle)) `chi bundle install loop-guard` (a plugin: breaks tool-call loops, see [Plugins](plugins.md#the-loop-guard-bundle)), `chi bundle install check-in` (a plugin: checks on a long turn, see [Plugins](plugins.md#the-check-in-bundle)), `chi bundle install slow-commands` (a plugin: commands that ran past the execute limit are run as tasks next time, see [Plugins](plugins.md#the-slow-commands-bundle)), `chi bundle install agents-md` (a plugin: a nested AGENTS.md is steered into the conversation on the first touch under its directory, see [Plugins](plugins.md#the-agents-md-bundle)) and `chi bundle install skills` (a plugin: `/skill`, versions of skills, see [Plugins](plugins.md#the-skills-bundle)).
|
|
595
|
+
- Shipped bundles (each in a profile: `core` or `dev`, see [Bundle profiles](memory.md#bundle-profiles-core-and-dev)): `chi bundle install guardrails` (rules, see [Guardrails](guardrails.md#the-guardrails-bundle)) and `chi bundle install known-names` (a hook, see [Guardrails](guardrails.md#the-known-names-bundle)), `chi bundle install source-links` (a hook: announces source refs, see [The source-links bundle](#the-source-links-bundle)), `chi bundle install btw` (a plugin: `/btw`, see [Plugins](plugins.md#the-btw-bundle)), `chi bundle install mcp` (a plugin: tools from MCP servers, see [Plugins](plugins.md#the-mcp-bundle)) `chi bundle install loop-guard` (a plugin: breaks tool-call loops, see [Plugins](plugins.md#the-loop-guard-bundle)), `chi bundle install check-in` (a plugin: checks on a long turn, see [Plugins](plugins.md#the-check-in-bundle)), `chi bundle install slow-commands` (a plugin: commands that ran past the execute limit are run as tasks next time, see [Plugins](plugins.md#the-slow-commands-bundle)), `chi bundle install agents-md` (a plugin: a nested AGENTS.md is steered into the conversation on the first touch under its directory, see [Plugins](plugins.md#the-agents-md-bundle)), `chi bundle install cost-cap` (a plugin: a per-session $ cap, `/cap`, see [Plugins](plugins.md#the-cost-cap-bundle)) and `chi bundle install skills` (a plugin: `/skill`, versions of skills, see [Plugins](plugins.md#the-skills-bundle)).
|
|
596
596
|
- Installing a bundle executes its hook code at `Engine` startup. Only install bundles you trust, as you would a gem. Hooks are **not** executed at install time (copy-only); they are `module_eval`'d at `Engine.new` inside per-bundle `Samagotchi::Bundles::<name>` namespaces (no top-level `require` collisions). Keep hook files side-effect-free at load time; do work in `#call` — top-level side effects (require, IO, `at_exit`, global assignment) run once per `Engine.new` (class redefinition is idempotent).
|
|
597
597
|
|
|
598
598
|
Lifecycle:
|
data/docs/memory.md
CHANGED
|
@@ -316,7 +316,7 @@ profiles, meta bundles that install a set of them:
|
|
|
316
316
|
|
|
317
317
|
| profile | bundles |
|
|
318
318
|
|---|---|
|
|
319
|
-
| `core` | loop-guard, check-in, guardrails, slow-commands, agents-md: the recommended safety set |
|
|
319
|
+
| `core` | loop-guard, check-in, guardrails, slow-commands, agents-md, cost-cap: the recommended safety set |
|
|
320
320
|
| `dev` | known-names, mcp, btw, skills, source-links, github-pr, coordinator, claude-peer, codex-peer |
|
|
321
321
|
|
|
322
322
|
`chi bootstrap` installs `core` and, on a terminal, offers `dev`. An existing
|
data/docs/plugins.md
CHANGED
|
@@ -730,6 +730,7 @@ full label, `plugin.rb (bundle my-bundle)`.
|
|
|
730
730
|
| `ctx.fork?` | whether the session is a fork (`ctx.sessions.fork`): it has a parent but isn't a delegate child, and started from a conversation, so its first prompt isn't its first user message |
|
|
731
731
|
| `ctx.model` | the model the session runs on now: its resolved ref (`host:id`), right after `/model` too |
|
|
732
732
|
| `ctx.model_key` | that model's memory overlay key (`<name>.<key>.md`, what `memory_write current_model_only` writes) |
|
|
733
|
+
| `ctx.cost` | the session's spend so far, read now, as a frozen value: `total` (USD, reported + estimated, side requests included; 0.0 before any), `reported` (by providers, `usage.cost`), `estimated` (from `hosts.<name>.models` prices; a generation is one or the other, never both), `side` (recap, `/btw`, `ask_model`: their share of `total`) and `known?` (false while generations or side requests ran but none was reported or priced, so a limit on it can't be enforced; a session with some priced generations reads known, the unpriced ones counting 0). It includes the running turn's finished generations and what earlier workers of the session spent. Use its methods, not its class. Needs chi 0.83.0 |
|
|
733
734
|
| `ctx.settings` | the bundle's settings, frozen |
|
|
734
735
|
| `ctx.data_dir` | `$XDG_STATE_HOME/samagotchi/plugins/<bundle>/`, created on first use. A session's own state goes in `sessions/<id>.json` or `sessions/<id>/` there: chi removes it when the session is deleted, discarded or pruned |
|
|
735
736
|
| `ctx.log` | `ctx.log.info(:event, key: value)`: debug-log records tagged `plugins`, with `bundle=<bundle>` |
|
|
@@ -1081,7 +1082,7 @@ CHANGELOG line and bumps the shipped bundles that use it. Anything else under
|
|
|
1081
1082
|
|
|
1082
1083
|
**Stable**
|
|
1083
1084
|
|
|
1084
|
-
1. `Samagotchi::Plugin::Api` / `chi.*` as documented above, including
|
|
1085
|
+
1. `Samagotchi::Plugin::Api` / `chi.*` and `ctx` as documented above (`ctx.cost`'s value by its methods), including
|
|
1085
1086
|
`Samagotchi::Plugin::ToolResult`, `Samagotchi::Plugin::ModelError` (and its
|
|
1086
1087
|
subclass `Samagotchi::Plugin::ModelCancelled`),
|
|
1087
1088
|
`Samagotchi::Plugin::Service::Stopped`, `Samagotchi::Plugin::Sessions::Error`
|
|
@@ -1134,6 +1135,7 @@ require "samagotchi/testing"
|
|
|
1134
1135
|
bundle = Samagotchi::Testing::Bundle.load("path/to/my-bundle", settings: { "every" => 3 },
|
|
1135
1136
|
host: { session_id: "s1", cwd: Dir.pwd })
|
|
1136
1137
|
bundle.fire(:after_turn, status: "completed") # → the event, after the hooks
|
|
1138
|
+
event = bundle.before_tool_call_event({ name: "read", path: "a.txt" }, params: "read a.txt")
|
|
1137
1139
|
bundle.command("/mine stop") # → what the command returned
|
|
1138
1140
|
bundle.tool("my_tool", path: "a.txt") # → the text the model reads
|
|
1139
1141
|
verdict = bundle.tool_call({ name: "execute", content: "rm -rf /" })
|
|
@@ -1144,6 +1146,7 @@ bundle.model_answers << "a summary" # the next ask_model's
|
|
|
1144
1146
|
bundle.sessions.on(:children) { |all:| [] } # ctx.sessions, scripted and recorded
|
|
1145
1147
|
bundle.http.stub(:post, "http://127.0.0.1:9000/classify", json: { label: "safe" }) # ctx.http, never the network
|
|
1146
1148
|
bundle.set(running: false) # the session's values: cwd:, messages:, cancelled: …
|
|
1149
|
+
bundle.set(cost: 2.5) # ctx.cost: reported USD, or { reported:, estimated:, side:, known: }
|
|
1147
1150
|
bundle.shutdown
|
|
1148
1151
|
```
|
|
1149
1152
|
|
|
@@ -1167,6 +1170,9 @@ bundle.shutdown
|
|
|
1167
1170
|
**fails the test at teardown** (RSpec and Minitest adapters: after the cleanup, and only a test that passed), so a
|
|
1168
1171
|
spec can't reach the network. With plain `Bundle.load` there is no teardown: check `bundle.http.unmatched`.
|
|
1169
1172
|
`bundle.set(cancelled: true)` makes a call without `cancel:` raise `HttpCancelled`.
|
|
1173
|
+
- **`ctx.cost`** is zero and known unless the test sets it: `bundle.set(cost: 2.5)` (reported USD), a Hash of the
|
|
1174
|
+
parts (`reported:`, `estimated:`, `side:`, `known:`; `total` is reported + estimated) or a callable giving either,
|
|
1175
|
+
called at each read (a counting lambda shows whether the bundle read it).
|
|
1170
1176
|
- **A hook that raises fails the call** (`Testing::HookFailed`, with the hook's error) where chi would log it and
|
|
1171
1177
|
go on; `bundle.lenient { … }` collects them in `bundle.errors` instead.
|
|
1172
1178
|
- **Sandbox.** A test runs with temp config, state, data and cache homes (config.yml, memories, `ctx.data_dir`, the
|
|
@@ -1541,6 +1547,57 @@ Not caught (yet):
|
|
|
1541
1547
|
- loops across turns;
|
|
1542
1548
|
- alternating calls (A, B, A, B) that each return something new.
|
|
1543
1549
|
|
|
1550
|
+
## The cost-cap bundle
|
|
1551
|
+
|
|
1552
|
+
`chi bundle install cost-cap` installs the bundle shipped with chi (`chi bootstrap` installs it with the `core`
|
|
1553
|
+
profile; an existing install gets it with `chi update`). It caps what one session spends, so neither you nor the
|
|
1554
|
+
model has to watch `chi sessions stats`: the harness watches the money and the model never sees any of it (no tool,
|
|
1555
|
+
no steer, no prompt text). It is inert without a cap, and there is none by default.
|
|
1556
|
+
|
|
1557
|
+
```yaml
|
|
1558
|
+
# config.yml
|
|
1559
|
+
bundles:
|
|
1560
|
+
cost-cap:
|
|
1561
|
+
default: 5.00 # USD cap for a session with none set; absent/0/off: no cap
|
|
1562
|
+
child: 2.00 # for delegate children; absent: default
|
|
1563
|
+
warn_at: 0.8 # tell the user (only) once at this share of the cap; 0/off: no warning
|
|
1564
|
+
```
|
|
1565
|
+
|
|
1566
|
+
- **What counts:** `ctx.cost`'s total: what the provider reported (`usage.cost`), else the estimate from the
|
|
1567
|
+
model's configured price (`hosts.<name>.models.<id>.price`), plus side requests (the idle recap, `/btw`,
|
|
1568
|
+
`ask_model`), including what earlier workers of the session spent.
|
|
1569
|
+
- **When it checks:** before each generation, so before the money is spent. A generation that crosses the cap is
|
|
1570
|
+
caught at the start of the next one: the overshoot is at most one generation (plus any side requests between).
|
|
1571
|
+
A turn whose last generation crossed it ends normally, and the next turn's first generation asks.
|
|
1572
|
+
- **At `warn_at` of the cap** you get one notice, `cost-cap> $2.41 of $3.00 spent (80%)`; a raised cap warns again.
|
|
1573
|
+
- **At the cap** the turn pauses on a question (header "Cost cap"): `Cost cap reached: $3.04 spent of $3.00.
|
|
1574
|
+
Continue raises it to $6.00.` with **Continue** (another cap's worth: $3 → $6 → $9, as many steps as it takes to
|
|
1575
|
+
pass the spend, saved for the session) and **Stop** (the turn stops, `canceled` with `stopped_by: cost-cap`, and a
|
|
1576
|
+
card shows the spend, reported, estimated and side). With no one to ask (`--non-interactive`) it stops.
|
|
1577
|
+
- **The cap of a session:** the one `/cap` or a Continue set for it, else `child:` for a delegate child, else
|
|
1578
|
+
`default:`. Delegates and continued links each have their own cap; there is no family cap.
|
|
1579
|
+
- **A host it can't watch:** a model that reports no cost and has no configured price (a local server, a free
|
|
1580
|
+
model that reports 0) reads unknown: one notice, `the $3.00 cap can't be enforced: <model> reports no cost and has
|
|
1581
|
+
no price`, and it never stops for an unknown cost. A session with some priced generations counts the unpriced
|
|
1582
|
+
ones as $0.
|
|
1583
|
+
|
|
1584
|
+
**`/cap`** (works while a turn runs; the next generation reads it):
|
|
1585
|
+
|
|
1586
|
+
- `/cap`: `cost-cap: $1.20 spent of $3.00 (reported $1.10, estimated $0.10, side $0.02)`, or `cost-cap: no cap
|
|
1587
|
+
($1.20 spent)`, with `(can't be enforced: no cost reported or priced)` when it can't be.
|
|
1588
|
+
- `/cap 5` or `/cap $5`: the cap (and what Continue adds) becomes $5.00 for this session. A cap at or under the
|
|
1589
|
+
spend is allowed: the next generation asks.
|
|
1590
|
+
- `/cap off` (or `/cap 0`): no cap for this session, over the config's `default:`.
|
|
1591
|
+
|
|
1592
|
+
A parent agent starts a session with a cap by sending `/cap N` as its first message (`chi send --new -m '/cap 3'`).
|
|
1593
|
+
At the cap, `chi send --wait` exits 3 with a question of kind `hook` (header "Cost cap"); `chi answer ID --question
|
|
1594
|
+
Q --option Continue` (or `--option 1`) goes on, `--option Stop` stops the turn ([sub-agent.md](sub-agent.md)). A
|
|
1595
|
+
parent may Continue: nothing restricts it, as with the step limit. A background delegate child at its cap waits on
|
|
1596
|
+
its own card and doesn't wake its parent ([sessions.md](sessions.md#delegating)).
|
|
1597
|
+
|
|
1598
|
+
The state is one file per session, `<data_dir>/sessions/<session id>.json` (`{"cap": 3.0, "step": 3.0}`), removed
|
|
1599
|
+
with the session.
|
|
1600
|
+
|
|
1544
1601
|
## The check-in bundle
|
|
1545
1602
|
|
|
1546
1603
|
`chi bundle install check-in` installs the bundle shipped with chi (`chi
|
data/docs/releasing.md
CHANGED
|
@@ -13,7 +13,7 @@ can run the whole release; the user approves the notes before the tag and the
|
|
|
13
13
|
and `rake release:check` enforce it). The system bundle upgrades itself when
|
|
14
14
|
chi starts.
|
|
15
15
|
- **Every other shipped bundle** (btw, check-in, guardrails, known-names,
|
|
16
|
-
loop-guard, mcp, skills, slow-commands, agents-md, source-links, github-pr, coordinator, claude-peer, codex-peer, and the profiles core and dev) has its own semver in its
|
|
16
|
+
loop-guard, mcp, skills, slow-commands, agents-md, cost-cap, source-links, github-pr, coordinator, claude-peer, codex-peer, and the profiles core and dev) has its own semver in its
|
|
17
17
|
`manifest.yml` and, when it needs a newer chi, a `requires_chi:` line. They never upgrade by themselves: users run
|
|
18
18
|
`chi update` (all of them, with the gem) or `chi bundle upgrade NAME`. So:
|
|
19
19
|
- a change to a bundle's files bumps that bundle's `version:`
|
data/docs/sessions.md
CHANGED
|
@@ -208,7 +208,7 @@ chi send --new --continues last:3fa2 -m "new day" # the next link of 3fa2's
|
|
|
208
208
|
- `--wait` takes one session: `--new` or one id. A session with a running turn is refused (`busy: a turn is running; wait or attach`, exit 1): the message would merge into it, and its reply would come back as the answer. `--queue --wait` is the way in: the message runs as a turn of its own after the running one, and the wait is for that turn alone (the ack's `enqueued_id` names its reply file and its `last_turn`), so the running turn's reply and end, and a question it asks meanwhile, are not the answer (it keeps waiting through them; its `--timeout` still applies); a question its own turn asks is (exit 3). Two queued waits each get their own turn's answer. A session waiting for an answer is still refused. `--format json` adds `delivery`: `queue`, or `next_step` when the running turn ended before the message went in (it then ran at once, and the answer is still its own).
|
|
209
209
|
- Exit codes with `--wait`: 0 answered; 3 the turn waits for an answer from you (a question, a hook's question, a guardrail approval, or the step-limit question: see [chi as a sub-agent](sub-agent.md#the-step-limit)), and the session keeps waiting: stderr gets the whole question (`waiting for an answer (approval): …`, its text, the numbered options, whether free text is allowed) and how to answer it (`chi answer ID --question QID --option N`, or `chi --attach ID`, the web); 4 `--timeout S` passed with the turn still running (it goes on; wait again); 1 the turn ended without a reply (canceled, failed or empty), the worker failed or vanished, or the session was stopped; 130 on Ctrl-C, which leaves the turn running (`still running: chi --attach ID`). There is no default timeout.
|
|
210
210
|
- `--format json` (with `--wait`) prints one JSON object on stdout however the wait ends, for a script or a parent agent (see [chi as a sub-agent](sub-agent.md)): `{"status":"answered","session_id":…,"text":…}`; `{"status":"question",…,"question":{"id","kind","header","text","options","multi_select","allow_freeform","approval"},"answer_with":"chi answer ID --question QID --option N"}`, where `kind` is `question` (the model's), `hook` or `approval` (with the tool, command, cwd, rule, reason and offered scopes); `{"status":"running",…,"detail":…}` (exit 4, and on Ctrl-C); and `failed`, `canceled`, `no_answer`, `error`, `worker_gone` or `stopped` with a `detail` line (exit 1); `failed` adds `error_kind` and `retryable`, `canceled` adds `cancel_reason` and `stopped_by`, when known. A failure before the wait (no such session, a busy one) is `error` too. A usage error (exit 2) prints no JSON.
|
|
211
|
-
- `--wait ID` with no message (no `-m`, nothing piped) sends nothing: it waits for the session's next reply and prints and exits as above. With a turn running, that turn's reply (its `interrupted_turn` mark names it), not a turn's queued behind it; with the step-limit question standing, the reply of the turn its Continue runs. With
|
|
211
|
+
- `--wait ID` with no message (no `-m`, nothing piped) sends nothing: it waits for the session's next reply and prints and exits as above. With a turn running, that turn's reply (its `interrupted_turn` mark names it), not a turn's queued behind it; with the step-limit question standing, the reply of the turn its Continue runs; with an idle session whose last turn already ended, that ended turn is the answer: its reply, or its end when it left no reply — a turn that finished in the gap between waits is reported, not waited for. With none of those (or a worker whose `bridge.json` lacks `turn_ids`), the next reply of any turn. A running session is fine (that is the point), so after exit 3, 4 or 130 an agent waits again this way while the user answers in the web or `chi --attach`; the question pending when it starts doesn't count again. An idle session with no worker waits until something wakes one. With `--new`, or two ids, it is a usage error (exit 2).
|
|
212
212
|
- `--continues ID` (with `--new`) starts the next link of that session's chain instead (see *Session chains*): in its folder, on its model (or the one `--model` names), LLM context, thinking level and toolkit, so `--dir`, `--llm-context*`, `--thinking`, `--toolkit`, `--tool` and `--no-tool` are refused beside it; `ID` may be a prefix or `last:ID`, the chain's latest link. Without a message the new link starts idle; `--wait` then has nothing to wait for (a usage error). It prints `<full id> started (continues 3fa2c1d0)`, with `; 2 delegates moved` inside the parentheses when open delegates moved to it; a refusal (continued already, naming the next link; an open delegate that can't move; a turn running) is `chi send: refused: …`, exit 1, and a `--model` it can't start on is `chi send: could not start a session: …`, exit 1, nothing archived. A fallback to the default model (below) prints `chi send: warning: …`.
|
|
213
213
|
- The 16 KiB cap applies: `git diff | chi send --new …` on a big diff is refused; name the branch in the message instead and let the session read it.
|
|
214
214
|
- A session nobody attaches to stalls at its first question or guardrail ask until someone answers it (`chi answer`, the web, `chi --attach`), and its worker stays up meanwhile: it doesn't idle-exit while a question waits (`chi sessions list` shows it as `waiting`; `chi sessions stop ID` frees it). Otherwise it idle-exits after `session.idle_exit_minutes` like any worker. These are ordinary sessions: delete them like any other.
|
|
@@ -253,6 +253,7 @@ The `delegate` tool hands a task to a **child session**: an ordinary chi session
|
|
|
253
253
|
- A wait that timed out or was canceled still gets its delegate report: neither moves the parent's cursor, so the child's reply comes as a report when its turn ends. The result says so (`chi brings its reply here by itself as a delegate report …`; with `queue`, that it joins the next step or turn) and that `delegate_result` waits again only if the turn needs the reply now. A parent that gets no reports (`session.delegate_reports: off`, a `--no-shared` REPL, `--non-interactive`) is told `delegate_result <id> waits again` instead, and `wait: false`'s result says `delegate_result waits for its reply`.
|
|
254
254
|
- What a wait returns, `session: <id>` then `status:` and the text, with the status words `chi send --wait --format json` uses: `answered` with the reply after a `---` line; `question` as soon as the child opens a question (the model's own, or a hook's), with the question's text, options and the `chi answer` command for it (nothing is answered for it: `chi answer`, attach or use the web, then wait again); `failed`, `canceled` or `no_answer` when the child's turn ended with nothing to say (its session shows what happened); `error` (its worker crashed) or `stopped` (`chi sessions stop`) at once; `worker_gone` when the child has had no worker for 15 seconds (it crashed without saying so, or idle-exited): `delegate` with `session:` starts it again; `running` on timeout, or when the parent's own turn is canceled (the child keeps running; the next turn can wait again). A reply saved before the child's worker idle-exited is returned at once.
|
|
255
255
|
- **A child's step-limit question goes back to the parent model**, not to its user: `delegate_result` returns `status: question` with the `chi answer` commands for Continue and Stop, and says the model may instead send a narrower follow-up (`delegate session:`, which drops the question) or report back. A parent's `chi answer` follows `turn.parent_continue` (see [chi as a sub-agent](sub-agent.md#the-step-limit)).
|
|
256
|
+
- **A child at its cost cap** (the [cost-cap bundle](plugins.md#the-cost-cap-bundle), the child's own cap: `child:` in its settings, or `/cap`) asks a `hook` question, "Cost cap": a parent waiting on it (`delegate`, `delegate_result`) gets it back as `status: question` with the `chi answer` commands, `--option Continue` or `--option Stop`. A **background** child (`wait: false`) paused at its cap does not wake its parent: delegate reports skip hook questions (they are the user's, on the child's card) and the approval relay carries approvals only, so the child waits on its card, with the web's bell. The parent sees it at its next `delegate_result`.
|
|
256
257
|
- **A child's approvals go to the parent's user** (the approval relay). While the parent waits (`delegate`, `delegate_result`), a guardrail approval the child opens reopens as the parent session's own approval: the same card in the parent's web page and attached terminal, with the same facts and diff, a line naming the delegate and its task (`delegate ab12 · fix the flaky spec…`, linked to its session) and the header `Approve delegate ab12's tool call?`. The options are the child's, by index; "for the session" says it is the delegate's session (the session scope keys on the child; repo and rule approvals are one shared store). The user answers once there; the parent's model never sees the approval, only one line per relayed approval in the tool's result, before the reply: `approval relayed to your user: execute: git push origin main → allowed once` (or `denied ("why")`, `dismissed (denied)`, `answered on the child`, `not delivered (the child's worker is gone)`, `refused by the child …`, `still open (your turn was stopped)`). The time the card is open doesn't count against `timeout`. While the parent waits on one child, its other running children's approvals are relayed too, oldest first, one card at a time (`+1 more delegate waiting`); each outcome comes in that child's next result.
|
|
257
258
|
- The child's own card stays answerable (the first answer wins: answered there, the parent's card closes as "answered in ab12"). While relayed it says `Waiting for approval in parent ab12: answering here works too`; `chi sessions list` shows the child as `waiting (in parent ab12)` (`--format json`: `relayed_to`), and the web's bell rings for the parent's card, not the child's.
|
|
258
259
|
- How the child trusts it: the parent's POST that says "answered" (Bridge `POST /session/<child>/relay`) carries no answer. The child asks its parent session's live Bridge (`POST /session/<parent>/relay/status`), which reports only what the parent's question flow took from a UI (`RelayDesk`, in memory only), and takes it only for this child, the question pending now, answered. A parent agent's answer on the relay card (a piped or marked `chi --attach PARENT`, `chi answer`) is held to `guardrails.parent_approvals` by the parent and again by the child.
|
data/docs/sub-agent.md
CHANGED
|
@@ -30,6 +30,7 @@ sources](context.md#peer-sources-the-peer-loop).
|
|
|
30
30
|
```sh
|
|
31
31
|
chi send --new --wait --format json [--dir D] [--model M] [--timeout S] -m "task"
|
|
32
32
|
chi send --wait --format json [--timeout S] [-m "follow-up"] ID
|
|
33
|
+
chi send --wait --format json [--timeout S] --turn T ID
|
|
33
34
|
chi send --queue --wait --format json [--timeout S] -m "follow-up" ID
|
|
34
35
|
chi answer ID --question QID (--option N|LABEL)... [--text T] [--timeout S] [--format json]
|
|
35
36
|
chi answer ID --question QID --dismiss [--timeout S] [--format json]
|
|
@@ -60,6 +61,11 @@ Exit codes, both commands:
|
|
|
60
61
|
|
|
61
62
|
With `--format json` stdout is one JSON object (see
|
|
62
63
|
[Starting a session](sessions.md#starting-a-session) for every field):
|
|
64
|
+
when the wait is about a known turn, it includes `turn_id`. Use
|
|
65
|
+
`chi send --wait --format json --turn <turn_id> <session_id>` to wait for
|
|
66
|
+
that exact turn again after exit 4, or after exit 3 if someone else will
|
|
67
|
+
answer the question. A turn id can name the running turn, a queued turn or
|
|
68
|
+
the last turn that ended; any other id is an error.
|
|
63
69
|
|
|
64
70
|
```json
|
|
65
71
|
{"status":"question","session_id":"297da360-…","question":{"id":"3f1c…","kind":"question",
|
|
@@ -99,6 +105,10 @@ A chi parent's `delegate_result` gets a child's step-limit question back as a
|
|
|
99
105
|
`delegate session:` (which drops the question), or report back. It isn't
|
|
100
106
|
relayed to the parent's user the way approvals are.
|
|
101
107
|
|
|
108
|
+
A session at its cost cap (the [cost-cap bundle](plugins.md#the-cost-cap-bundle)) waits the same way, as a
|
|
109
|
+
question of kind `hook` with header "Cost cap" and options `Continue` and `Stop`: `--option Continue` raises the cap
|
|
110
|
+
by another cap's worth and the turn goes on, `--option Stop` ends it `canceled` with `stopped_by: cost-cap`.
|
|
111
|
+
|
|
102
112
|
## Approvals
|
|
103
113
|
|
|
104
114
|
A chi parent (the `delegate` tool) doesn't go through any of this: its
|
|
@@ -115,7 +125,7 @@ a parent may deny it (`--option Deny --text "why"`, `--text` alone, or
|
|
|
115
125
|
and tell your user`. For an approval, the question block and `answer_with` give
|
|
116
126
|
the deny command, not `--option N` or `chi --attach`. The block also offers to
|
|
117
127
|
leave it open: tell your user it waits in chi web (the bell and badge show
|
|
118
|
-
it there), and `chi send --wait --format json ID`
|
|
128
|
+
it there), and `chi send --wait --format json --turn <turn_id> ID` waits until
|
|
119
129
|
they answer it. With
|
|
120
130
|
`guardrails.parent_approvals: once` in chi's config.yml (it has no environment
|
|
121
131
|
variable), "Allow once" goes through; the wider scopes never do,
|
|
@@ -161,7 +171,11 @@ answers from any local process.
|
|
|
161
171
|
- `chi send --wait ID` with no message waits past a question already reported
|
|
162
172
|
(the step-limit question too), until someone answers it. It waits for the
|
|
163
173
|
turn running when it starts (or the one the step-limit question continues),
|
|
164
|
-
and prints that turn's reply, not a queued one's after it.
|
|
174
|
+
and prints that turn's reply, not a queued one's after it. To an idle
|
|
175
|
+
session whose last turn already ended (no question standing), that ended
|
|
176
|
+
turn is the answer: its reply comes back (`answered`), or its end when it
|
|
177
|
+
left no reply (`failed`, `canceled`, `limit`, …) — so a turn that finished
|
|
178
|
+
in the gap between waits is reported, not waited for.
|
|
165
179
|
- An answer to a question that is no longer open (you answered it in the web
|
|
166
180
|
first) isn't sent; `chi answer` says so and waits for what comes next. Its
|
|
167
181
|
JSON then has `"answered_here": false` (absent when the answer went in), so
|
|
@@ -185,18 +199,19 @@ Start a task: `chi send --new --wait --format json --timeout 500 -m "<task>"`
|
|
|
185
199
|
user, showing them the question and its options; if you can't ask (no user in
|
|
186
200
|
this run), stop and report the question and options instead of guessing. To
|
|
187
201
|
answer, run the `answer_with` command with `--option N` (1-based, repeat for
|
|
188
|
-
multi-select) and/or `--text "…"`. It returns the same JSON. If you
|
|
189
|
-
|
|
190
|
-
|
|
202
|
+
multi-select) and/or `--text "…"`. It returns the same JSON. If you leave it
|
|
203
|
+
open for someone else to answer, wait again with `--turn <turn_id>` from the
|
|
204
|
+
JSON. If you won't answer, add `--dismiss` instead: chi does nothing it asked
|
|
205
|
+
about, finishes with what it found, and the reply comes back (exit 0).
|
|
191
206
|
- `question.kind: approval`: chi wants to run something its user's guardrails
|
|
192
207
|
flagged, and allowing it is up to your user. Deny it (the `answer_with`
|
|
193
208
|
command: `--option Deny --text "why"`), and tell your user what it wanted to
|
|
194
209
|
run and why. Or leave it open: tell your user it waits in chi web (the
|
|
195
|
-
session's short id), then `chi send --wait --format json --timeout 500 <session_id>`
|
|
210
|
+
session's short id), then `chi send --wait --format json --timeout 500 --turn <turn_id> <session_id>`
|
|
196
211
|
waits until they answer it there. Never allow it yourself, through
|
|
197
212
|
`chi answer`, a piped `chi --attach` or chi's web API.
|
|
198
|
-
- `status: running` (exit 4): still working. Wait again:
|
|
199
|
-
`chi send --wait --format json --timeout 500 <session_id>`.
|
|
213
|
+
- `status: running` (exit 4): still working. Wait for that same turn again:
|
|
214
|
+
`chi send --wait --format json --timeout 500 --turn <turn_id> <session_id>`.
|
|
200
215
|
- `question.kind: continue`: chi ran out of steps before it answered. If it is
|
|
201
216
|
getting somewhere, continue it (`answer_with`, `--option Continue`, with
|
|
202
217
|
`--text "…"` to steer it on the way); if not,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: core
|
|
3
|
-
version: 0.3.
|
|
3
|
+
version: 0.3.2
|
|
4
4
|
scope: system
|
|
5
5
|
description: "Recommended safety set"
|
|
6
6
|
trust_level: reviewed
|
|
7
|
-
|
|
7
|
+
# Its members' highest floor (cost-cap's)
|
|
8
|
+
requires_chi: ">= 0.83.0"
|
|
8
9
|
includes:
|
|
9
10
|
- loop-guard
|
|
10
11
|
- check-in
|
|
11
12
|
- guardrails
|
|
12
13
|
- slow-commands
|
|
13
14
|
- agents-md
|
|
15
|
+
- cost-cap
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cost-cap
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
scope: system
|
|
5
|
+
description: "A per-session $ cap: at the cap the turn pauses on Continue (another cap's worth) / Stop, a warning at 80% goes to the user only; /cap shows or sets it"
|
|
6
|
+
trust_level: reviewed
|
|
7
|
+
plugin:
|
|
8
|
+
file: plugin.rb
|
|
9
|
+
sha256: sha256:bc6683af0063b90d6a3d7c2796bccf446043dfb1b3f07c04bd54d22d26199e1d
|
|
10
|
+
# ctx.cost is new in chi 0.83.0
|
|
11
|
+
requires_chi: ">= 0.83.0"
|