samagotchi 0.17.0 → 0.18.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 +64 -1
- data/docs/architecture.md +25 -2
- data/docs/cli.md +34 -8
- data/docs/configuration.md +4 -2
- data/docs/guardrails.md +51 -8
- data/docs/hooks.md +4 -1
- data/docs/memory.md +5 -3
- data/docs/plugins.md +3 -2
- data/docs/sessions.md +6 -5
- data/docs/sub-agent.md +42 -4
- data/lib/samagotchi/answer_command.rb +23 -2
- data/lib/samagotchi/answer_tail.rb +52 -0
- data/lib/samagotchi/bridge.rb +49 -8
- data/lib/samagotchi/bridge_client.rb +18 -3
- data/lib/samagotchi/bundle_command.rb +108 -2
- data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +18 -3
- data/lib/samagotchi/bundles/guardrails/manifest.yml +4 -4
- data/lib/samagotchi/bundles/system/config_modification_protocol.md +1 -1
- data/lib/samagotchi/bundles/system/manifest.yml +2 -2
- data/lib/samagotchi/commands/registry.rb +28 -0
- data/lib/samagotchi/config.rb +7 -1
- data/lib/samagotchi/continue_offer.rb +177 -0
- data/lib/samagotchi/engine.rb +42 -14
- data/lib/samagotchi/guardrails/outside.rb +46 -0
- data/lib/samagotchi/guardrails/parent_approvals.rb +2 -0
- data/lib/samagotchi/guardrails/parent_continue.rb +43 -0
- data/lib/samagotchi/guardrails/rules.rb +17 -5
- data/lib/samagotchi/guardrails/shell_git_dirs.rb +267 -0
- data/lib/samagotchi/guardrails/targets.rb +30 -13
- data/lib/samagotchi/guardrails.rb +2 -0
- data/lib/samagotchi/iteration_limit.rb +38 -0
- data/lib/samagotchi/kernel_loop.rb +2 -1
- data/lib/samagotchi/llm/backend.rb +2 -1
- data/lib/samagotchi/llm/chat_loop.rb +2 -1
- data/lib/samagotchi/llm/native_backend.rb +2 -1
- data/lib/samagotchi/memory_bundle/trash.rb +81 -2
- data/lib/samagotchi/parent_report.rb +39 -9
- data/lib/samagotchi/question_desk.rb +205 -46
- data/lib/samagotchi/reply_wait.rb +10 -4
- data/lib/samagotchi/send_command.rb +2 -1
- data/lib/samagotchi/session.rb +3 -2
- data/lib/samagotchi/session_commands.rb +3 -2
- data/lib/samagotchi/session_manager.rb +4 -1
- data/lib/samagotchi/sessions_command.rb +3 -2
- data/lib/samagotchi/system_prompt.rb +1 -1
- data/lib/samagotchi/terminal_ui/attached_loop.rb +25 -5
- data/lib/samagotchi/terminal_ui/question_prompt.rb +34 -2
- data/lib/samagotchi/terminal_ui.rb +11 -2
- data/lib/samagotchi/tool_declarations.rb +2 -2
- data/lib/samagotchi/tools/delegate_wait.rb +14 -5
- data/lib/samagotchi/tools/edit.rb +6 -1
- data/lib/samagotchi/tools/execute.rb +5 -4
- data/lib/samagotchi/tools/read.rb +2 -1
- data/lib/samagotchi/turn_flow.rb +8 -7
- data/lib/samagotchi/turn_note.rb +7 -0
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/app.rb +187 -42
- data/lib/samagotchi/web/public/app.js +122 -25
- data/lib/samagotchi/web/public/data.js +12 -13
- data/lib/samagotchi/web/public/index.html +6 -1
- data/lib/samagotchi/web/public/notify.js +5 -2
- data/lib/samagotchi/web/public/question_card.js +28 -1
- data/lib/samagotchi/web/public/question_cards.js +74 -3
- data/lib/samagotchi/web/public/sessions_list.js +1 -0
- data/lib/samagotchi/web/public/timing.js +41 -0
- data/lib/samagotchi/web/public/turn_events.js +61 -0
- data/lib/samagotchi/web/public/update_notice.js +15 -0
- data/lib/samagotchi/worker.rb +30 -58
- data/lib/samagotchi/worker_sidecar.rb +6 -3
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9c7c0dfd8a859bb4808e10128560904cac788bc6bed8ec0b9d799425c6eee2fb
|
|
4
|
+
data.tar.gz: 4263cf99ea0998c74d7ae0f4e37d3bdbed8acc0140400082eed696da58e14847
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16b8b6cda0d263cb6540e052d207483396a5fd4588e3e4ddd776e02bcc107fb63ef73e19835b20b44e5c95e850d30b018bf985d898c8a2b07e6ae7a7a2984034
|
|
7
|
+
data.tar.gz: ade20718b92e00ee24274d5b0cdce3ff102ae88487cb00af244168134f1f0c0d3259cb1e7f19ade71bf8fe442cba1a764bd6550c5d1911ef7cce558fabac044f
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,68 @@ and commands may change between minor versions. How releases are made:
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.18.0] - 2026-10-03
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- The guardrails bundle (0.4.0) asks before git that changes another checkout: `cd ../main && git commit`,
|
|
16
|
+
`git -C ../main add .`, an `execute` with `cwd:` there (commit, add, reset, checkout, switch, rebase, merge, push,
|
|
17
|
+
stash, rm, mv, cherry-pick, revert, pull, restore, am). Read-only git and tests there still run unasked. Its card
|
|
18
|
+
offers once, this session, or "rule in this repo" (stored, so later sessions there don't ask); switch it off with
|
|
19
|
+
`guardrails.disable: [git-outside-repo]`. Run `chi update` (or `chi bundle upgrade guardrails`) to get it.
|
|
20
|
+
- Web: a tab left open across a `chi web` upgrade says so: when its event stream reconnects to a newer chi, a toast
|
|
21
|
+
`chi was updated to X` offers a Reload button (it never reloads by itself; ✕ dismisses it).
|
|
22
|
+
- `chi bundle trash` lists the bundle trash (moved files from uninstalls/upgrades) oldest first, with file count,
|
|
23
|
+
size, and age; `--empty` deletes all folders, `--older-than DAYS` keeps only recent ones, `--dry-run` previews.
|
|
24
|
+
- A turn that runs out of iterations now waits as a question (kind `continue`, the step-limit question):
|
|
25
|
+
`chi send --wait` exits 3 with it, `chi answer --option Continue|Stop` answers it, and the web badge and bell show
|
|
26
|
+
it. The web draws it as a card with Continue, Stop and a reason for Stop; `chi --attach` reads the continue words
|
|
27
|
+
at its prompt (Enter continues). A parent agent may Continue unless `turn.parent_continue: false` (config.yml only);
|
|
28
|
+
a delegate's goes back to the parent model.
|
|
29
|
+
- `turn.max_iterations` (default 100, env `SAMAGOTCHI_TURN_MAX_ITERATIONS`) sets the per-turn step limit;
|
|
30
|
+
`--no-interrupt` turns get the larger of 1000 and it.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- `write-outside-repo` and any `path: outside_repo` rule measure from the session's repo (not a plugin call's own
|
|
35
|
+
`cwd:`), with symlinks resolved: `/tmp/x` and `/private/tmp/x` are the same
|
|
36
|
+
folder, and a link in the repo that points elsewhere counts as outside. Writes into tmp folders (`$TMPDIR`, `/tmp`)
|
|
37
|
+
no longer ask, unless the session itself lives there. Hook events' `targets` gain `git_dirs`.
|
|
38
|
+
- A one-word `/word` that no command answers (`/modle`) is no longer sent to the model as a prompt: every UI
|
|
39
|
+
(the REPL, the attached TUI, the web composer) prints `Unknown command /modle. Did you mean /model? /help lists
|
|
40
|
+
the commands.` instead, with the "Did you mean" part only when a command name is close. Lines that are prompts
|
|
41
|
+
(`/foo bar`, `/usr/bin/env is missing`) still go to the model.
|
|
42
|
+
- `chi send --wait` reports `limit` instead of `no_answer` when a turn ran out of iterations and nobody can answer
|
|
43
|
+
(an older worker), with how to continue it.
|
|
44
|
+
- Stop at the step limit (`/continue no`) keeps the turn's work, with a note for the model, instead of erasing the
|
|
45
|
+
prompt and everything the turn did; `!rollback` still erases it. Reminder turns follow `turn.max_iterations`.
|
|
46
|
+
- The web page's end-of-turn and card re-reads fetch only what they show: the session's state, the last answer,
|
|
47
|
+
that turn's timings and the cards, from a new light worker read (`GET /session/:id/tail`) instead of the
|
|
48
|
+
whole conversation and timing history, so they stay the same size however long the session runs.
|
|
49
|
+
- `execute` gives a command 120 s by default (was 30 s) before it is killed; `execute.timeout_sec` /
|
|
50
|
+
`SAMAGOTCHI_EXECUTE_TIMEOUT_SEC` still set it.
|
|
51
|
+
- An `execute` command killed at its time limit says how to run a long one: a second line
|
|
52
|
+
`(killed at the limit; for a long command use task_create, then task_wait)` follows `Error: command timed out after Ns`.
|
|
53
|
+
- `read` with only `end_line` reads from line 1 instead of failing with "start_line must be provided". A range
|
|
54
|
+
`edit` with only `end_line` still refuses (it would overwrite the top of the file) and now says what to pass:
|
|
55
|
+
`pass start_line too (1-based, the first line to replace)`.
|
|
56
|
+
- In a worktree or subfolder, the system prompt's "Project root" line now says it is only where shared project
|
|
57
|
+
memories come from, and to read, edit, run and commit in the current working directory; a model took the root for
|
|
58
|
+
its workspace and committed to the main checkout.
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- Web: a session waiting on a plugin card (check-in's Nudge / Keep going / Stop) shows its "needs you" badge as a
|
|
63
|
+
pill on its session card again; it was drawn as a large empty circle over the card.
|
|
64
|
+
- Web: with a prompt queued behind a turn, the next turn's live timing line no longer freezes or takes the
|
|
65
|
+
previous turn's number when the previous turn's re-read lands late.
|
|
66
|
+
- `chi sessions list` shows the first message of a session that has no turn yet, such as one started with
|
|
67
|
+
`chi send --new -m "/model x"` or from the web start page; its row was blank.
|
|
68
|
+
- Web: a terminal-only command (`/stats`, `/exit`, `/recap` …) typed as the start page's first message is answered on
|
|
69
|
+
the start page; it no longer starts a session just to show that reply.
|
|
70
|
+
|
|
71
|
+
Update with `chi update`: the guardrails bundle moves to 0.4.0 (the `git-outside-repo` rule; it needs chi 0.18.0).
|
|
72
|
+
|
|
11
73
|
## [0.17.0] - 2026-10-03
|
|
12
74
|
|
|
13
75
|
### Added
|
|
@@ -940,7 +1002,8 @@ and long-lived sessions.
|
|
|
940
1002
|
- A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
|
|
941
1003
|
hotkey panel that send selected text or the clipboard to your sessions.
|
|
942
1004
|
|
|
943
|
-
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.
|
|
1005
|
+
[Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.18.0...HEAD
|
|
1006
|
+
[0.18.0]: https://github.com/dm1try/samagotchi/compare/v0.17.0...v0.18.0
|
|
944
1007
|
[0.17.0]: https://github.com/dm1try/samagotchi/compare/v0.16.0...v0.17.0
|
|
945
1008
|
[0.16.0]: https://github.com/dm1try/samagotchi/compare/v0.15.0...v0.16.0
|
|
946
1009
|
[0.15.0]: https://github.com/dm1try/samagotchi/compare/v0.14.0...v0.15.0
|
data/docs/architecture.md
CHANGED
|
@@ -177,7 +177,7 @@ rescues sink errors). A new UI (web, API) supplies its own `on_event` and
|
|
|
177
177
|
renders whatever it needs from the stream + final `Result`. The public Engine API:
|
|
178
178
|
|
|
179
179
|
```ruby
|
|
180
|
-
engine.run_turn(session, prompt, on_event: nil, max_iterations:
|
|
180
|
+
engine.run_turn(session, prompt, on_event: nil, max_iterations: nil, cancel_controller: nil) # nil: turn.max_iterations
|
|
181
181
|
engine.run(session: nil, prompt: "...", on_event: nil) # create/resume session + run
|
|
182
182
|
engine.system_prompt # fully built system prompt string
|
|
183
183
|
engine.session # current session (Engine owns create/resume)
|
|
@@ -256,7 +256,13 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
|
|
|
256
256
|
- `GET /session/:id/state` — `session_state_snapshot` (JSON).
|
|
257
257
|
- `GET /session/:id/stats` — `Engine#stats_snapshot` for attached `/stats`: the metrics, with the context window and prompt profile asked from the server before the first turn.
|
|
258
258
|
- `GET /session/:id/snapshot` — the snapshot frame's content as one request (the web server renders
|
|
259
|
-
the messages itself, then streams from its `event_seq`).
|
|
259
|
+
the messages itself, then streams from its `event_seq`). Read by the web's full show
|
|
260
|
+
(`GET /api/sessions/:id`, `?parts=1`) and the plugin's `list_sessions`; every message is copied.
|
|
261
|
+
- `GET /session/:id/tail` — the page's light re-read (`Bridge#tail_frame`): `session_state_snapshot`,
|
|
262
|
+
`answer` (the last message a UI shows as an answer, `AnswerTail`: the one message, copied alone),
|
|
263
|
+
`cards` (`CardStore#list`), `event_seq`, `event_id`, all taken in one event-log hold; no message
|
|
264
|
+
list, so it costs the same however long the session is. `?turn_id=` answers that turn's answer
|
|
265
|
+
(an unknown or missing id: the newest); the only route that reads its query.
|
|
260
266
|
- `OPTIONS *` — CORS preflight (`Access-Control-Allow-Origin: *`).
|
|
261
267
|
|
|
262
268
|
The metrics in `/state`, `/snapshot`, `/stats` and the SSE `snapshot`/`reset` frames
|
|
@@ -265,6 +271,21 @@ newest turn the worker finished and any it hasn't saved yet (at most 20 turns),
|
|
|
265
271
|
calls and the running turn's finished ones. The full history is the session's `analytics.json`,
|
|
266
272
|
which `SessionMetrics#persist` rewrites after each turn; the web server merges the two by id.
|
|
267
273
|
|
|
274
|
+
The page's frequent reads of `GET /api/sessions/:id` stay light too:
|
|
275
|
+
- `?tail=1&recent=1` (each turn's end, a cancel, an answer display): `{tail, session: {id, status,
|
|
276
|
+
used_memory_names}, messages: [the answer], markdown_warning, timing}`, from the worker's `/tail`
|
|
277
|
+
alone (no session file parsed, no `analytics.json`); `timing` carries the worker's recent records
|
|
278
|
+
and `turn_count` (finished turns). The page merges the records by id (`timing.js mergeTiming`) and
|
|
279
|
+
finishes the ended turn's own line by its `turn_id` (sent as `&turn_id=`, which also picks that
|
|
280
|
+
turn's answer). A plain `?tail=1` (a tab opened before this) gets the whole timing.
|
|
281
|
+
- `?timing=1`: the whole timing (`analytics.json` merged with the worker's `/state`) and
|
|
282
|
+
`turn_count`, when the page's merge came up short.
|
|
283
|
+
- `?cards=1`: the cards, from `/tail`; the session file is only stat'ed for the 404.
|
|
284
|
+
|
|
285
|
+
A worker without `/tail` (an older chi; `404 not_found`) is read through `/snapshot` in the same
|
|
286
|
+
shape; any other failure (no worker, a 500, a timeout) reads the disk, trimmed to the newest
|
|
287
|
+
turn's records with `recent=1`.
|
|
288
|
+
|
|
268
289
|
Every route goes through `Bridge#dispatch`: an id other than the bridge's own session is
|
|
269
290
|
`404 unknown_session`, and a handler that raises answers `500 bridge_error` (logged as
|
|
270
291
|
`handler_failed`) rather than dropping the connection.
|
|
@@ -280,6 +301,8 @@ never pulls from the page: `chi web` runs one `Samagotchi::Web::SessionHub` (a t
|
|
|
280
301
|
the server, no daemon) that keeps an in-memory projection of the session list and pushes
|
|
281
302
|
changes to every open tab over `GET /api/events` (SSE: a `snapshot` frame on every connect,
|
|
282
303
|
then `session` for an upsert and `session_gone` for a removal, `: ping` while idle, no replay).
|
|
304
|
+
The snapshot names chi's version: a tab served by another one (its `<body data-version>`) offers a
|
|
305
|
+
reload in a toast, since an open tab keeps its old JS across a `chi web` upgrade.
|
|
283
306
|
Files stay the source of truth and workers don't know the hub. Its watcher is a 1 s tick that
|
|
284
307
|
stats the sessions dir (every session writer goes tmp + rename, which bumps the dir's mtime) and
|
|
285
308
|
each `<id>/` folder (recap.json, bridge.json, the stopped and archived markers), re-parsing only the files whose mtime or size
|
data/docs/cli.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
- `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))
|
|
24
24
|
- `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`). `chi self --model` prints only the model a new session starts on, an alias resolved to its target (the desktop helper's hint)
|
|
25
25
|
- `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))
|
|
26
|
-
- `chi bundle install|upgrade|uninstall|status|diff|list|build` — 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)). 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
|
|
26
|
+
- `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
27
|
|
|
28
28
|
### First setup
|
|
29
29
|
|
|
@@ -285,7 +285,10 @@ In an attached terminal:
|
|
|
285
285
|
worker, and every UI sees their output; `/stats` and `/recap` work too. The
|
|
286
286
|
Web UI's composer takes the same commands, and so do `-p "/model x"` and
|
|
287
287
|
`chi send -m "/model x"`. A `/word` the session doesn't know (a path, a
|
|
288
|
-
typo) goes to the model as a prompt
|
|
288
|
+
typo) goes to the model as a prompt; a one-word `/word` that no command
|
|
289
|
+
answers (`/modle`) is not sent: the UI prints `Unknown command /modle. Did
|
|
290
|
+
you mean /model? /help lists the commands.` (the "Did you mean" part only
|
|
291
|
+
when a name is close).
|
|
289
292
|
- `!commands` and the model's tools run in the session's directory (where it
|
|
290
293
|
was started), whichever terminal you attach from.
|
|
291
294
|
- `-p` sends its prompt once attached, `--model` switches the worker's model
|
|
@@ -397,7 +400,9 @@ REPL alike:
|
|
|
397
400
|
- `/stats` and `/recap` answer at once, and in an attached terminal so do `/help`
|
|
398
401
|
and a plugin's anytime command (`/btw`). Other commands (`!cmd`, `/model`, `/models`,
|
|
399
402
|
`!rollback`, `/continue`, `/guardrails`) say `busy: wait for the turn to end`
|
|
400
|
-
and go back into the prompt, so Enter runs them once the turn ends.
|
|
403
|
+
and go back into the prompt, so Enter runs them once the turn ends. A
|
|
404
|
+
one-word `/word` no command answers (`/modle`) is not steering text either:
|
|
405
|
+
the hint prints and the line is dropped.
|
|
401
406
|
- A question (`ask_user_question`, a guardrails approval) turns the prompt into
|
|
402
407
|
a yellow `? ` and lists its choices under it, fitted to the terminal; only a
|
|
403
408
|
line submitted there answers it (a number, `1,3`, a label, `y`/`n` for an
|
|
@@ -803,8 +808,29 @@ Behavior notes:
|
|
|
803
808
|
|
|
804
809
|
## Iteration Limit Behavior
|
|
805
810
|
|
|
806
|
-
-
|
|
807
|
-
|
|
808
|
-
-
|
|
809
|
-
-
|
|
810
|
-
-
|
|
811
|
+
- A turn's step limit (model ↔ tool rounds) is `turn.max_iterations` in config.yml (env
|
|
812
|
+
`SAMAGOTCHI_TURN_MAX_ITERATIONS`, default `100`, an integer of 1 or more). `--no-interrupt` and
|
|
813
|
+
`--non-interactive` turns get the larger of 1000 and it. Reminder turns follow it too.
|
|
814
|
+
- Tool side effects that already ran before the limit are not rolled back.
|
|
815
|
+
- `Samagotchi::KernelLoop#run` returns a resumable result object with the visible output plus the accumulated
|
|
816
|
+
conversation. If the limit is reached while tool calls are still pending, the result is marked resumable so callers
|
|
817
|
+
can continue from the saved conversation instead of restarting from scratch. The session's `last_turn` records it
|
|
818
|
+
(`"exhausted": true, "limit": N`).
|
|
819
|
+
- In a session's worker (plain `chi`, the web, `chi send`) the offer waits as a question between turns, the
|
|
820
|
+
**step-limit question** (kind `continue`, header "Step limit", options Continue / Stop): the session reads
|
|
821
|
+
`waiting` in the lists, the web shows a card with Continue and Stop and a reason box (a reason goes with Stop), the
|
|
822
|
+
session card's badge reads `out of steps` and the bell rings ("hit its step limit"). `chi send --wait` exits 3 with
|
|
823
|
+
it and `chi answer --option Continue|Stop` answers it (see [chi as a sub-agent](sub-agent.md)). It can't be
|
|
824
|
+
dismissed. A new message instead (or a due reminder) drops it and the partial turn stays; a typed `/continue …`
|
|
825
|
+
answers it too. If the worker exits (a stop, a crash), the offer goes with it.
|
|
826
|
+
- In the attached terminal (`chi --attach`) the question's prompt reads the continue words: Enter alone, `yes`, `y`
|
|
827
|
+
or `/continue` continue; `no` stops; `no, <reason>` stops and tells the model why; `1` and `2` pick the options.
|
|
828
|
+
- In the plain REPL (`--no-shared`) the CLI asks at the `? ` prompt, with the choices listed under it: `yes` (Enter
|
|
829
|
+
alone, or `/continue`) resumes, `no` stops, and `no, <explanation>` stops while keeping the reason in conversation
|
|
830
|
+
context. Once answered, one line stays: `? The turn ran out of iterations. Continue it? → no, too slow`.
|
|
831
|
+
- Stop keeps the interrupted turn's work in the conversation, as a Ctrl-C or a new prompt does, with a note for the
|
|
832
|
+
model that it wasn't continued (or your reason); `!rollback` still erases it.
|
|
833
|
+
- `--non-interactive` (`chi -p`) has nobody to ask: the turn ends there. A `chi send --wait` on a session nobody can
|
|
834
|
+
answer (an older worker) reports `limit` (exit 1) with how to continue it: `chi send ID -m '/continue yes'`.
|
|
835
|
+
- The check-in bundle's card closes as the turn ends, before the step-limit question opens, so the two never stand
|
|
836
|
+
together; with check-in's default `after: 50` and the limit of 100 a long turn usually shows a check-in card first.
|
data/docs/configuration.md
CHANGED
|
@@ -869,6 +869,8 @@ described in their own sections.
|
|
|
869
869
|
| `retry.base_delay` | `0.5` | yes | |
|
|
870
870
|
| `retry.max_delay` | `8.0` | yes | |
|
|
871
871
|
| `retry.empty_answer` | `1` | | Times a turn asks again after an empty answer (at most 3, `0` = off). See "Llama Network Retry Behavior". |
|
|
872
|
+
| `turn.max_iterations` | `100` | | 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). |
|
|
873
|
+
| `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). |
|
|
872
874
|
| `update.gem` | `true` | | `false`: `chi update` never installs a newer gem (`--no-gem` for one run). See [CLI: Updating](cli.md#updating). |
|
|
873
875
|
| `update.bundles` | `true` | | `false`: `chi update` leaves the shipped bundles to `chi bundle upgrade` (`--no-bundles`). |
|
|
874
876
|
| `update.desktop` | `true` | | `false`: `chi update` leaves the desktop helper alone (`--no-desktop`). |
|
|
@@ -882,14 +884,14 @@ described in their own sections.
|
|
|
882
884
|
| `execute.truncate_at_bytes` | `65536` | yes | The same for `execute` output. |
|
|
883
885
|
| `execute.preview_bytes` | `12288` | yes | |
|
|
884
886
|
| `execute.telemetry_threshold_pct` | `80` | yes | |
|
|
885
|
-
| `execute.timeout_sec` | `
|
|
887
|
+
| `execute.timeout_sec` | `120` | yes | Seconds one `execute` command may run before it is stopped. |
|
|
886
888
|
| `web.port` | `4567` | `--port` | `chi web`'s port. See [CLI](cli.md). |
|
|
887
889
|
| `web.host` | `127.0.0.1` | yes | `127.0.0.1`, `::1` or `localhost`; `lan` (this machine's private IPv4 address) or one of its IPv4 addresses also opens `chi web` to the network, with an access token (`chi web --new-token` replaces it). Anything else binds `127.0.0.1` with a warning. See [CLI: chi web on your phone](cli.md#chi-web-on-your-phone). |
|
|
888
890
|
| `web.markdown` | `false` | yes | Render answers as Markdown in `chi web`. |
|
|
889
891
|
| `web.view` | `stage` | yes | How `chi web` draws a turn: `stage` (the running turn pinned above the composer) or `turn` (one block per turn in the history). See [CLI](cli.md#web-views). |
|
|
890
892
|
| `web.annotate_presets` | `Agreed\|Could you please elaborate?` | yes | Quick replies next to Annotate in `chi web`, `\|`-separated (a YAML list works too); `""` in the file or on the CLI leaves only Annotate (an empty env value means the default). See [CLI](cli.md#web-annotate-presets). |
|
|
891
893
|
| `history.file` | state dir | | Prompt history path, shared by the TUI and the web composer. |
|
|
892
|
-
| `no_interrupt` | `false` | `--no-interrupt` | Raise the
|
|
894
|
+
| `no_interrupt` | `false` | `--no-interrupt` | Raise the step limit of a turn to 1000 (or `turn.max_iterations`, when larger); a top-level key. |
|
|
893
895
|
| `no_default_input` | `false` | `--no-default-input` | Don't pre-fill `default.input`; a top-level key. |
|
|
894
896
|
| `skip_agent_md` | `false` | | Don't load AGENT.md; a top-level key (see below). |
|
|
895
897
|
|
data/docs/guardrails.md
CHANGED
|
@@ -118,6 +118,12 @@ directory, the rule) stay with the web and the terminal. An approval that
|
|
|
118
118
|
doesn't offer "once" can't be allowed from a parent, and neither can one whose
|
|
119
119
|
offered scopes are missing or don't fit its options: then only Deny goes.
|
|
120
120
|
|
|
121
|
+
A child that has to work in another folder (`write-outside-repo`,
|
|
122
|
+
`git-outside-repo`) is denied this way too. Start it in that folder instead
|
|
123
|
+
(`chi send --new --dir PATH`), or have your user allow "rule … in this repo"
|
|
124
|
+
once on the web (it is stored for later sessions in that repo), or switch the
|
|
125
|
+
rules off: `guardrails: {disable: [write-outside-repo, git-outside-repo]}`.
|
|
126
|
+
|
|
121
127
|
Whatever the setting, a parent can't allow a call on chi's own files: a
|
|
122
128
|
`write`, `edit` or `execute` on the config dir (config.yml, installed bundles'
|
|
123
129
|
rules), the hooks dir or the approval store, by its rule (`chi-config`,
|
|
@@ -126,6 +132,11 @@ config.yml rewrite would allow everything from then on, so those stay with the
|
|
|
126
132
|
user: `chi answer` and the worker refuse the allow with `only the user can
|
|
127
133
|
allow it`.
|
|
128
134
|
|
|
135
|
+
A turn's step-limit question (kind `continue`) isn't an approval: Continue
|
|
136
|
+
grants no permission, so a parent may answer it either way by default.
|
|
137
|
+
`turn.parent_continue: false` (config.yml only, like `parent_approvals`)
|
|
138
|
+
makes parents stop-only. See [chi as a sub-agent](sub-agent.md#the-step-limit).
|
|
139
|
+
|
|
129
140
|
The guardrails settings (`enabled`, `small_models`, `parent_approvals`) are
|
|
130
141
|
read from config.yml only: they have no environment variable, and a worker
|
|
131
142
|
chi starts unsets every `SAMAGOTCHI_GUARDRAILS_*` it would inherit. What a
|
|
@@ -182,9 +193,16 @@ guardrails:
|
|
|
182
193
|
tool: [write, edit]
|
|
183
194
|
path: outside_repo # or a glob: "**/.git/hooks/**", "/etc/**", "config/*.yml"
|
|
184
195
|
verdict: ask
|
|
185
|
-
reason: writes outside
|
|
196
|
+
reason: writes outside this session's repo
|
|
197
|
+
- id: git-outside-repo
|
|
198
|
+
tool: shell
|
|
199
|
+
git: outside_repo # a shell call runs commit/add/reset/… in another checkout
|
|
200
|
+
verdict: ask
|
|
201
|
+
scopes: [once, session, rule]
|
|
186
202
|
```
|
|
187
203
|
|
|
204
|
+
A rule gives at least one of `tool`, `command`, `path` and `git`.
|
|
205
|
+
|
|
188
206
|
A tool name may be a glob, so one rule covers a plugin's tools (an MCP server's,
|
|
189
207
|
say): `tool: "mcp_*"` or `tool: ["mcp_{git,gh}_*", web_fetch]` (`*`, `?`, `[…]` and
|
|
190
208
|
`{a,b}`, matched with `File.fnmatch`). `/guardrails` lists the glob as given.
|
|
@@ -203,11 +221,30 @@ All the fields a rule gives must match. Absolute and `**/` globs match the
|
|
|
203
221
|
resolved path; other globs match the path relative to the repo root. Paths
|
|
204
222
|
resolve the way the tools resolve them (against the cwd; `~` expanded).
|
|
205
223
|
|
|
206
|
-
`outside_repo`
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
224
|
+
`outside_repo` is measured from the session's repo: the git work tree its
|
|
225
|
+
folder is in (a linked worktree is its own), or the folder itself outside a
|
|
226
|
+
repo. Not the call's own `cwd:`: an `execute` with `cwd:` in another checkout
|
|
227
|
+
is outside. Symlinks are resolved on both sides (`/tmp` and `/private/tmp` are
|
|
228
|
+
one folder; a link in the repo that points elsewhere is outside). Never outside:
|
|
229
|
+
|
|
230
|
+
- a memory's file: a `*.md` right in the project or system memories folder (not
|
|
231
|
+
`index.md`, not a hidden file). `write`/`edit` there is what `memory_write`
|
|
232
|
+
does unasked, and the model is told to `edit` a memory for a small change.
|
|
233
|
+
The memories folder's `index.md`, `.bundles/`, and the rest of chi's config
|
|
234
|
+
folder still count as outside, as does chi's state folder (sessions,
|
|
235
|
+
approvals);
|
|
236
|
+
- a tmp folder (`$TMPDIR`, `/tmp`, `/var/tmp`), unless the session's repo is
|
|
237
|
+
itself in that tmp folder: a sandbox there still gets asked about its siblings.
|
|
238
|
+
|
|
239
|
+
`git: outside_repo` (shell tools only) reads the command for git subcommands
|
|
240
|
+
that change a checkout: `commit`, `add`, `reset`, `checkout`, `switch`,
|
|
241
|
+
`rebase`, `merge`, `push`, `stash` (not `stash list`/`show`), `rm`, `mv`,
|
|
242
|
+
`cherry-pick`, `revert`, `pull`, `restore`, `am`. It follows `cd`,
|
|
243
|
+
`pushd`/`popd`, `( … )`, `git -C`, `--git-dir`/`--work-tree`, `GIT_DIR`/`GIT_WORK_TREE`
|
|
244
|
+
and the call's `cwd:`, and matches when one of them runs outside the session's
|
|
245
|
+
repo (the same `outside_repo` as above). `cd /other && git status`, `git -C
|
|
246
|
+
/other log` and `cd /other && bundle exec rspec` don't match. A folder the text
|
|
247
|
+
doesn't tell (`cd $REPO`, `cd "$(…)"`, `cd -`) doesn't either; see Limits.
|
|
211
248
|
|
|
212
249
|
To switch off single rules (a bundle's, say) without editing its files, list
|
|
213
250
|
them under `disable:`. A plain id switches off every rule with that id; `bundle:id`
|
|
@@ -268,7 +305,10 @@ installs a default rule set plus a short memory telling the model not to
|
|
|
268
305
|
route around a deny. It asks before `git push`, `reset --hard`, `clean -f`,
|
|
269
306
|
`branch -D`, `rebase`, `filter-branch`/`filter-repo`; `rm -rf` on `/`, `~`,
|
|
270
307
|
`$HOME` or `..` paths; `curl … | sh` and `base64 -d … | sh`; writes outside the
|
|
271
|
-
repo
|
|
308
|
+
session's repo (`write-outside-repo`) and git that changes another checkout
|
|
309
|
+
(`git-outside-repo`: `cd ../main && git commit`, `git -C ../main add .`; it
|
|
310
|
+
offers once, this session and "rule in this repo", the last stored for later
|
|
311
|
+
sessions in that repo); shell commands that name chi's config, hooks or guardrails or
|
|
272
312
|
`.git/hooks`; and answering chi's questions around `chi answer`: `chi --attach`
|
|
273
313
|
or `chi -p` with stdin from a pipe, a here-string or a file
|
|
274
314
|
(`chi-answer-piped`), and `curl`/`wget` to a session's `/answer` route
|
|
@@ -354,6 +394,9 @@ it as `~` or `$HOME`, so the model rarely has to spell it.
|
|
|
354
394
|
|
|
355
395
|
Text matching on shell commands stops accidents, not a model set on getting
|
|
356
396
|
around it: `sh -c`, base64, a script written earlier, `git -C` variants and
|
|
357
|
-
aliases can get past a regex.
|
|
397
|
+
aliases can get past a regex. `outside_repo` covers the file tools and git
|
|
398
|
+
only: `sed -i`, `cp` or `cat > file` into another folder from the shell aren't
|
|
399
|
+
asked about, and neither is git run through `sh -c`, a script, an alias, `make`
|
|
400
|
+
or a folder in a variable. `!cmd` lines typed by you are not checked. The only
|
|
358
401
|
real defence against an adversarial model is isolation (a sandbox, a git
|
|
359
402
|
identity without push rights).
|
data/docs/hooks.md
CHANGED
|
@@ -341,7 +341,10 @@ the voter's own (a guard that wants the model to retry a corrected call:
|
|
|
341
341
|
`event[:context]` is `{cwd:, repo_root:, branch:, session_id:, interface:, origin:}`
|
|
342
342
|
(`interface` is `:repl`, `:worker` or `:non_interactive`). `event[:targets]` is
|
|
343
343
|
what the call acts on, resolved as the tools resolve it:
|
|
344
|
-
`{command:, paths:, cwd:, repo_root:, outside_repo:}`.
|
|
344
|
+
`{command:, paths:, cwd:, repo_root:, outside_repo:, git_dirs:}`. `outside_repo`
|
|
345
|
+
is measured from the session's repo, not the call's `cwd:`; `git_dirs` lists where
|
|
346
|
+
a shell call runs git that changes a checkout (`"unknown"` for a folder the
|
|
347
|
+
text doesn't tell; `[]` for other tools).
|
|
345
348
|
|
|
346
349
|
The older flag still works: `event[:blocked] = true` with an optional
|
|
347
350
|
`event[:block_reason]`. It is folded into the verdict after each hook (so it
|
data/docs/memory.md
CHANGED
|
@@ -103,9 +103,11 @@ for a file another bundle installed.
|
|
|
103
103
|
and the output names them and the dir:
|
|
104
104
|
`Moved to the trash: notes.md (…/.trash/team-notes-20261003-120000)`. So
|
|
105
105
|
does an upgrade's removal of a file the new version no longer ships. A
|
|
106
|
-
file you edited still needs `--force`.
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
file you edited still needs `--force`. `chi bundle trash` lists the trash
|
|
107
|
+
(oldest first, with file count, size, and age); `--empty` deletes all
|
|
108
|
+
folders, `--older-than DAYS` keeps only recent ones, and `--dry-run`
|
|
109
|
+
previews. Hooks, rules and the plugin live in the bundle's own dir and
|
|
110
|
+
are deleted with it.
|
|
109
111
|
- **A file two bundles list stays** until the last of them is uninstalled
|
|
110
112
|
(`Kept identity.md: bundle samagotchi-system has it too`); its index line
|
|
111
113
|
then names the bundle that still has it.
|
data/docs/plugins.md
CHANGED
|
@@ -229,8 +229,9 @@ chi's own. Path and command rules need to know what a call acts on, and that
|
|
|
229
229
|
is what `targets:` says:
|
|
230
230
|
|
|
231
231
|
- `paths:`: files the call reads or writes, absolute or relative to `cwd:`
|
|
232
|
-
(else the session's directory). `path:` globs, `outside_repo`
|
|
233
|
-
|
|
232
|
+
(else the session's directory). `path:` globs, `outside_repo` (measured
|
|
233
|
+
from the session's repo, whatever the call's `cwd:`) and the protected
|
|
234
|
+
paths (chi's config, …) match them.
|
|
234
235
|
- `command:`: a shell command the call runs; `command:` rules match it.
|
|
235
236
|
- `cwd:`: where it runs, for the repo root and relative paths.
|
|
236
237
|
|
data/docs/sessions.md
CHANGED
|
@@ -13,7 +13,7 @@ Sessions are plain files — no DB. Each session is `~/.local/state/samagotchi/s
|
|
|
13
13
|
|
|
14
14
|
A session is deleted if **expired by age OR overflow by count** (unless `keep_status` or the live-owner guard: a session a worker or `chi` still has open is never pruned). A session's `status` is its turn state (`idle`/`running`), not whether a worker is alive. Orphan dirs without a `*.json` are never deleted, except skeleton-only ones (see *Empty sessions*). Deletion removes both `*.json` and sidecar dir atomically. Set both `DAYS=0` and `MAX=0` to retain forever.
|
|
15
15
|
|
|
16
|
-
**Worker idle exit:** a background worker (plain `chi`, Web UI sessions) exits after `SAMAGOTCHI_SESSION_IDLE_EXIT_MINUTES` (`session.idle_exit_minutes`, default `30`, `0`=never) with no turn running or queued, no client on its stream (an open web tab or attached terminal keeps it), no reminder registered and no continue offer waiting for an answer. It removes `bridge.json` and frees `owner.lock`; the next prompt or `--attach` wakes a new worker (`WorkerIdleExit`, `SessionManager.run_session_loop`). A client can also ask the worker to exit right away (Bridge `POST /session/:id/exit`; `/exit` in an attached terminal does, `/detach` doesn't): the same rules apply without the timeout (even with `0`), and the asker's own stream doesn't count. The reply names what keeps it up. The session's status stays as it was, not `stopped`.
|
|
16
|
+
**Worker idle exit:** a background worker (plain `chi`, Web UI sessions) exits after `SAMAGOTCHI_SESSION_IDLE_EXIT_MINUTES` (`session.idle_exit_minutes`, default `30`, `0`=never) with no turn running or queued, no client on its stream (an open web tab or attached terminal keeps it), no reminder registered and no continue offer (the step-limit question) waiting for an answer. It removes `bridge.json` and frees `owner.lock`; the next prompt or `--attach` wakes a new worker (`WorkerIdleExit`, `SessionManager.run_session_loop`). A client can also ask the worker to exit right away (Bridge `POST /session/:id/exit`; `/exit` in an attached terminal does, `/detach` doesn't): the same rules apply without the timeout (even with `0`), and the asker's own stream doesn't count. The reply names what keeps it up. The session's status stays as it was, not `stopped`.
|
|
17
17
|
|
|
18
18
|
**Empty sessions:** a session nothing happened in is deleted when it is left, so `chi` → `/exit` leaves no trace. Empty means: no messages, no turn tried (a failed turn leaves a `last_prompt` and keeps it), no memory used, no pending question, nothing in `input/`, `notes/` or `images/`, nothing in its dir beyond the worker's skeleton, mode `assist`, and the default model. A session on another model (`/model`, `--model`) counts as prepared and stays (`SessionManager.discardable?`). The worker checks as it idle-exits or leaves on a client's request and deletes it once its lock is free, checking again first. The attached `/exit` then says `Detached; the session was empty, so it is discarded.` The REPL (`--no-shared`) does the same at `/exit` or Ctrl-D. The retention sweep catches those killed first: empty sessions over an hour old with no live owner, whatever `DAYS`/`MAX`, and skeleton-only dirs with no `*.json`. `session.keep_empty: true` (env `SAMAGOTCHI_SESSION_KEEP_EMPTY`) turns all of it off.
|
|
19
19
|
|
|
@@ -23,7 +23,7 @@ A session is deleted if **expired by age OR overflow by count** (unless `keep_st
|
|
|
23
23
|
|
|
24
24
|
**Deleting a session:** `chi sessions delete ID...`, `/exit --delete` in a terminal and the Web UI's delete all go through `SessionManager.delete_session`: it resolves a unique prefix, removes `<id>.json` and the whole `<id>/` dir, and returns what it removed. A session a plain REPL has open (`owner.lock` kind `tui`) is always refused. One a worker runs is refused unless the caller asks to stop it: then it stops the worker as `chi sessions stop` does and deletes once `owner.lock` is free (`--force` on the CLI, 10 s; the web always, 2 s; `/exit --delete` after the worker agreed to exit, 10 s). A worker that outlives the wait leaves the session in place. The web's route is `DELETE /api/sessions/:id` (200 `{status: "deleted", session_id, stopped}`; 409 `owned_by_tui` or `still_stopping`; 404).
|
|
25
25
|
|
|
26
|
-
<a id="archiving-a-session"></a>**Archiving a session:** `chi sessions archive ID...`, `/archive` in a terminal and the Web UI's `archive` go through `SessionManager.archive_session`: it writes `<id>/archived` (`{"archived_at": …}`; not a session.json field, which every save rewrites) for the session and its delegated children. `Session.list` leaves archived sessions out unless `include_archived:`, and everything built on it follows: `chi sessions list` (`--archived` shows them, `[archived]`), the summaries (`--live`, json/tsv, the desktop panel, the agent's `list_sessions`) and the retention prune, which neither deletes nor counts them against `max_count`. `children_of` (max_children, `delegate_result`) still sees them, and `--resume`/`--attach ID`, `chi send`/`chi note ID` reach them as before. Refused: a turn running in the session or in one of its children (naming the child; 409 `busy` on the web), a plain REPL owning it (`owned_by_tui`), a `chi scratch` session (`scratch`). A live idle worker is stopped first, and the marker is written even if it is still shutting down. Input a human typed brings a session back (`{"unarchived_at": …}`): a prompt from the web, an attached or plain terminal or `chi send` (origin `web:*`, `tui:*`, `cli:send`, none), steering merged into a running turn, a `/continue` answer or a question answered; a delegate's follow-up, a plugin's turn, a reminder, the idle recap, a note and just opening it don't. A child's input brings only the child back. The retention sweep ages an unarchived session from when it was unarchived, so unarchiving an old session doesn't let the next sweep take it. `/archive` leaves the session first, as `/exit` does, then archives it: in an attached terminal the worker is asked to exit (one that stays up for another UI is stopped by the archive; a running turn refuses it), and an empty session is discarded instead; `chi scratch` refuses it. `chi sessions unarchive ID...` and the web's `unarchive` bring back the whole family. Web routes: `POST /api/sessions/:id/archive` (200 `{status, session_id, archived, stopped, discarded}`, ids) and `POST /api/sessions/:id/unarchive` (200 `{status, session_id, unarchived}`). The session hub and `GET /api/sessions` keep archived sessions, `archived: true` in their summaries: the page leaves them out of the strip, its count and the list at render, and "include archived" by the all-sessions search shows them, dimmed with an `archived` badge. The info bar reads `archive · stop · delete` (`unarchive` on an archived one); archiving the open session stays on it, with an Undo toast.
|
|
26
|
+
<a id="archiving-a-session"></a>**Archiving a session:** `chi sessions archive ID...`, `/archive` in a terminal and the Web UI's `archive` go through `SessionManager.archive_session`: it writes `<id>/archived` (`{"archived_at": …}`; not a session.json field, which every save rewrites) for the session and its delegated children. `Session.list` leaves archived sessions out unless `include_archived:`, and everything built on it follows: `chi sessions list` (`--archived` shows them, `[archived]`), the summaries (`--live`, json/tsv, the desktop panel, the agent's `list_sessions`) and the retention prune, which neither deletes nor counts them against `max_count`. `children_of` (max_children, `delegate_result`) still sees them, and `--resume`/`--attach ID`, `chi send`/`chi note ID` reach them as before. Refused: a turn running in the session or in one of its children (naming the child; 409 `busy` on the web), a plain REPL owning it (`owned_by_tui`), a `chi scratch` session (`scratch`). A live idle worker is stopped first, and the marker is written even if it is still shutting down. Input a human typed brings a session back (`{"unarchived_at": …}`): a prompt from the web, an attached or plain terminal or `chi send` (origin `web:*`, `tui:*`, `cli:send`, none), steering merged into a running turn, a `/continue` answer or a question answered (the step-limit question too); a delegate's follow-up, a plugin's turn, a reminder, the idle recap, a note and just opening it don't. A child's input brings only the child back. The retention sweep ages an unarchived session from when it was unarchived, so unarchiving an old session doesn't let the next sweep take it. `/archive` leaves the session first, as `/exit` does, then archives it: in an attached terminal the worker is asked to exit (one that stays up for another UI is stopped by the archive; a running turn refuses it), and an empty session is discarded instead; `chi scratch` refuses it. `chi sessions unarchive ID...` and the web's `unarchive` bring back the whole family. Web routes: `POST /api/sessions/:id/archive` (200 `{status, session_id, archived, stopped, discarded}`, ids) and `POST /api/sessions/:id/unarchive` (200 `{status, session_id, unarchived}`). The session hub and `GET /api/sessions` keep archived sessions, `archived: true` in their summaries: the page leaves them out of the strip, its count and the list at render, and "include archived" by the all-sessions search shows them, dimmed with an `archived` badge. The info bar reads `archive · stop · delete` (`unarchive` on an archived one); archiving the open session stays on it, with an Undo toast.
|
|
27
27
|
|
|
28
28
|
**Lazy sweep:** automatic prune runs only while `chi web` runs, at most once per `session.sweep_interval_hours` (default 24): the web's session hub gives it a chance on its periodic full scan, and `GET /api/sessions` does too. No cron, and a terminal-only chi never sweeps. Manual prune is always available.
|
|
29
29
|
|
|
@@ -40,7 +40,7 @@ chi sessions prune [--dry-run] [--days N] [--keep N] [--keep-status running,...]
|
|
|
40
40
|
chi sessions clean [--dry-run] [--days N] # test sessions: all, or older than N days
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
A session whose worker waits for an answer (a question, a hook's question or a
|
|
43
|
+
A session whose worker waits for an answer (a question, a hook's question, a guardrail approval, or the step-limit question of a turn that ran out of steps, which waits between turns) reads `waiting` in the status column, and json has `waiting: "question"|"approval"|"hook"|"continue"` and `waiting_id`, the question's id for `chi answer --question` (both null otherwise). A delegate's approval its parent relayed to its own card (see *Delegating*) reads `waiting (in parent ab12)`, json `relayed_to: "ab12…"` (the parent's short id, else null). Only while a worker runs it: a question a dead worker left in the file shows the saved status. Answer it with `chi answer`, the web or `chi --attach ID`; `chi sessions stop ID` frees the worker instead.
|
|
44
44
|
|
|
45
45
|
Examples:
|
|
46
46
|
|
|
@@ -63,7 +63,7 @@ chi sessions clean --dry-run --days 7 # test sessions older than 7 da
|
|
|
63
63
|
**Ordering:**
|
|
64
64
|
|
|
65
65
|
- `Session.list` / `SessionManager.list_sessions` / `GET /api/sessions?sort=&order=&limit=&offset=` default to `updated_at desc` (newest activity first). Also supports `created_at`, `asc`. `X-Total-Count` header when paginated.
|
|
66
|
-
- Web UI (`chi web`): the page's scope is in its URL. Started in a git repo, `chi web` opens `/?dir=<that folder>`: that project's sessions, and new chats start in that folder; the header chip says `<project> · all`, and `all` opens the same place without `?dir` (every session; a new chat there starts in the server's own folder, shown on the start page, and cards name their folder; `← <project>` goes back to the project view it came from, or to the server's own project). One server serves every project: a second `chi web` (from another repo) finds it through `GET /api/info` and prints (with `--open`, opens) its page for its own folder instead of starting another. The 3 latest sessions sit above the chat (`hide ▴` puts them away behind a `▾ N sessions` pill, remembered; a window under 700 px tall hides them by itself, so a question or approval card in the stage keeps room, and the pill shows them until the window is tall again); "All sessions" (or `/`) opens every session at `#/sessions`, with a search over preview, id and status (Esc or Back returns). The open session is in the URL (`#/s/<id>`), so a reload or a copied link opens it again; the chi logo top left goes back to the empty start for a new chat. The message box grows with its text; drag its top edge to keep it taller (double-click resets). The info bar copies `chi --attach <id>` for a terminal. The start page's model picker (bottom left of the composer, the chosen name `▾`) chooses the model a new chat starts on; it opens a search list over the conversation (↓ or a click; downward when the start page leaves no room above): an empty search shows the last 5 picks (Recent) and then each host's model ids A–Z under the host, the default host first; typed words must each match the host or the id (a substring, else for 3+ characters the letters in order, so `deepseek4.1 fla` finds `openrouter · deepseek/deepseek-v4.1-flash`), ⏎ picks, Esc/Tab close, a pick puts the focus in the message box: `GET /api/models` lists the hosts' models as chi spells them (`{default, models: [{name, host, id}], warning?}`; bare for the default host, `host:model` for the others, from the same cached lists as `/models`, a bounded wait, a host that is down noted in `warning`), and `POST /api/sessions` takes `model` (blank means the default). The start page's first message goes as later ones do: `POST /api/sessions` with `idle: true` and `preview` (the message, which names the session until its turn is saved), then `POST /api/sessions/:id/turn`, so a failed first turn puts its prompt back in the composer too; a first line that is one of the new session's commands (`/model …`) goes to `POST /api/sessions/:id/command` instead, as later ones do. The browser remembers the last choice; a running session's model is in the info bar and changes only with `/model`. In the composer a line is a command when it is `!…` or starts with one of the session's command names (its `commands` list; `/stats`, `/exit` and the other terminal words get the page's own reply); an unknown `/word` (`/usr/bin/env is missing`) goes to the model as a prompt, as in a terminal. A `POST /turn` (or a session's first `prompt`) that is one of the worker's commands runs as that command too (`202 {status, command_id, session_id}`), so `chi send`, `chi -p` and a page whose command list lags all get the command. ↑/↓ in the composer walk the prompt history the TUI's ↑ walks ([Persistent Prompt History](cli.md#persistent-prompt-history)): ↑ with the caret on the first line, ↓ from the last line while browsing, ↓ past the newest brings the draft back; `GET /api/history` returns `{entries: [...]}`, oldest first (the page fetches it on focus and after each send). The server records a sent line in the routes once the worker took it (`POST /turn` accepted, a `!command` through `POST /command` or as a session's first line); `"history": false` in the body keeps it out (an image-only send).
|
|
66
|
+
- Web UI (`chi web`): the page's scope is in its URL. Started in a git repo, `chi web` opens `/?dir=<that folder>`: that project's sessions, and new chats start in that folder; the header chip says `<project> · all`, and `all` opens the same place without `?dir` (every session; a new chat there starts in the server's own folder, shown on the start page, and cards name their folder; `← <project>` goes back to the project view it came from, or to the server's own project). One server serves every project: a second `chi web` (from another repo) finds it through `GET /api/info` and prints (with `--open`, opens) its page for its own folder instead of starting another. The 3 latest sessions sit above the chat (`hide ▴` puts them away behind a `▾ N sessions` pill, remembered; a window under 700 px tall hides them by itself, so a question or approval card in the stage keeps room, and the pill shows them until the window is tall again); "All sessions" (or `/`) opens every session at `#/sessions`, with a search over preview, id and status (Esc or Back returns). The open session is in the URL (`#/s/<id>`), so a reload or a copied link opens it again; the chi logo top left goes back to the empty start for a new chat. The message box grows with its text; drag its top edge to keep it taller (double-click resets). The info bar copies `chi --attach <id>` for a terminal. The start page's model picker (bottom left of the composer, the chosen name `▾`) chooses the model a new chat starts on; it opens a search list over the conversation (↓ or a click; downward when the start page leaves no room above): an empty search shows the last 5 picks (Recent) and then each host's model ids A–Z under the host, the default host first; typed words must each match the host or the id (a substring, else for 3+ characters the letters in order, so `deepseek4.1 fla` finds `openrouter · deepseek/deepseek-v4.1-flash`), ⏎ picks, Esc/Tab close, a pick puts the focus in the message box: `GET /api/models` lists the hosts' models as chi spells them (`{default, models: [{name, host, id}], warning?}`; bare for the default host, `host:model` for the others, from the same cached lists as `/models`, a bounded wait, a host that is down noted in `warning`), and `POST /api/sessions` takes `model` (blank means the default). The start page's first message goes as later ones do: `POST /api/sessions` with `idle: true` and `preview` (the message, which names the session until its turn is saved), then `POST /api/sessions/:id/turn`, so a failed first turn puts its prompt back in the composer too; a first line that is one of the new session's commands (`/model …`) goes to `POST /api/sessions/:id/command` instead, as later ones do. The browser remembers the last choice; a running session's model is in the info bar and changes only with `/model`. In the composer a line is a command when it is `!…` or starts with one of the session's command names (its `commands` list; `/stats`, `/exit` and the other terminal words get the page's own reply); an unknown `/word` (`/usr/bin/env is missing`) goes to the model as a prompt, as in a terminal; a one-word `/word` no command answers (`/modle`) is not sent: the page shows `Unknown command /modle. Did you mean /model? /help lists the commands.` (the "Did you mean" part only when a name is close). A `POST /turn` (or a session's first `prompt`) that is one of the worker's commands runs as that command too (`202 {status, command_id, session_id}`), so `chi send`, `chi -p` and a page whose command list lags all get the command. ↑/↓ in the composer walk the prompt history the TUI's ↑ walks ([Persistent Prompt History](cli.md#persistent-prompt-history)): ↑ with the caret on the first line, ↓ from the last line while browsing, ↓ past the newest brings the draft back; `GET /api/history` returns `{entries: [...]}`, oldest first (the page fetches it on focus and after each send). The server records a sent line in the routes once the worker took it (`POST /turn` accepted, a `!command` through `POST /command` or as a session's first line); `"history": false` in the body keeps it out (an image-only send).
|
|
67
67
|
- Web notifications: when a session needs you while the tab is hidden or behind another window (a question or a guardrail approval, a plugin card with buttons during a turn such as check-in's, a failed turn, a turn done after 10 s or more; a delegated session only for its questions, a canceled turn never), the page title counts it, `(N) Chi`, until you come back to the tab. The bell at the right of the top bar also turns on OS notifications ("needs an answer", "needs approval", "needs you", "turn failed", "done in 42 s", under the session's first message; a click opens the session): its first click asks the browser for the permission, and the choice is kept per browser. Any session of the page's scope counts, not only the open one. What was already so when the page loaded never counts, and several chi tabs show one notification per event. A chi tab in front tells the others (a `BroadcastChannel`), so while you look at one, the ones behind neither notify nor count what it shows; coming back to a tab clears those events in the other tabs' titles too. The hub's `session` frames carry what this needs: `pending_question` (`{id, kind}`, kind `question` or `approval`), `pending_card` (`{id, bundle}`: the running turn's card with actions, from `pending_card.json` that the worker keeps in the session's folder while one is open; a card without actions, such as loop-guard's, never counts) and `last_turn` (`{outcome, ended_at, seconds, origin}`, saved in the session's JSON as the engine ends each turn).
|
|
68
68
|
- The web frontend is a zero-build ES-module stack in `lib/samagotchi/web/public/`: `data.js` (retrieval, typed SSE `openStream` for a session and `openEvents` for the session list), `sessions_list.js` (the list as a pure reducer over `GET /api/events`: a snapshot replaces it, an upsert keeps a known card in place, a removal drops it), `notify.js` (which of those events needs the user, for the notifications), `app.js` (presentation/state; a session with no live stream gets one when its `session` event says `bridge_up`, after one re-read: `event_seq` starts over in each worker, so a dropped stream is never resumed with its old cursor), `format.js` (pure formatters such as `previewOf`), `prompt_history.js` (the composer's ↑/↓ over the shared prompt history). Unit-tested via `npm test` (`node --test spec/web/public/*.test.js`).
|
|
69
69
|
- Selecting a session in the web UI is read-only: `GET /api/sessions/:id` never spawns a worker (it reads a live worker's snapshot when one runs, else the session file). A prompt (`POST /turn`) or a command (`POST /command`) wakes the worker, and `/stream` briefly waits for a freshly-spawned bridge before answering. A caught-up SSE reconnect holds the stream open; `reset` markers are only sent for reconnects behind the ring window, or with a cursor from another worker (event ids are `<event_seq>-<epoch>`, one epoch per worker).
|
|
@@ -169,7 +169,7 @@ chi send --wait 3fa2 # sends nothing: waits fo
|
|
|
169
169
|
- `--new` takes no ids (one new session per call). `--dir DIR` is its folder, and so its project (default the current one); `--model M` its model (default the configured one; the name isn't checked up front, a wrong one fails the worker's first turn).
|
|
170
170
|
- `--new` prints `<full id> started` on stdout. With `--wait` that line (and the `sent` line for an existing session) goes to stderr, so stdout is exactly the answer: the reply of the turn the message started, in full. It is the worker's `output/` file, the same one `delegate` reads; a turn that ends with only tool calls and no text has none.
|
|
171
171
|
- `--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 run after it, and its reply would come back as the answer.
|
|
172
|
-
- Exit codes with `--wait`: 0 answered; 3 the turn waits for an answer from you (a question, a hook's question
|
|
172
|
+
- 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.
|
|
173
173
|
- `--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). A failure before the wait (no such session, a busy one) is `error` too. A usage error (exit 2) prints no JSON.
|
|
174
174
|
- `--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. 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).
|
|
175
175
|
- 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.
|
|
@@ -182,6 +182,7 @@ The `delegate` tool hands a task to a **child session**: an ordinary chi session
|
|
|
182
182
|
- `delegate(task, model:, session:, wait:, timeout:)`: starts a child and, by default, waits for its reply (`wait: false` returns at once, so several children can run side by side; `timeout`, default 600 s, leaves a slow child running; 0 checks once without waiting). With `session:` (a child's id or prefix) the task goes to that child as a follow-up instead, delivered like `chi send` (client id `delegate:<parent short id>`, shown as `delegate>` in the child's UIs); only this session's children take one.
|
|
183
183
|
- `delegate_result(session:, timeout:)`: waits for a child's next reply: the one named, or the newest running child (`timeout` as for `delegate`: 0 checks once and returns at once). The reply comes back once; a later call waits for the next one.
|
|
184
184
|
- 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.
|
|
185
|
+
- **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)).
|
|
185
186
|
- **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.
|
|
186
187
|
- 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.
|
|
187
188
|
- 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
|
@@ -34,9 +34,10 @@ Exit codes, both commands:
|
|
|
34
34
|
|---|---|---|
|
|
35
35
|
| 0 | `answered` | `text` is chi's reply |
|
|
36
36
|
| 0 | `command` | (`chi send`) the message was a session command (`/model x`): it ran as one, no reply comes |
|
|
37
|
+
| 0 | `not_continued` | (`chi answer`) a Stop answered the step-limit question; the turn's work so far stays |
|
|
37
38
|
| 3 | `question` | chi waits for an answer: `question`, `answer_with` |
|
|
38
39
|
| 4 | `running` | `--timeout` passed; the turn goes on, wait again |
|
|
39
|
-
| 1 | `failed`, `canceled`, `no_answer`, `error`, `worker_gone`, `stopped` | `detail` says what happened |
|
|
40
|
+
| 1 | `failed`, `canceled`, `limit`, `no_answer`, `error`, `worker_gone`, `stopped` | `detail` says what happened (`limit`: the turn ran out of steps and nobody can be asked, e.g. an older worker; `chi send ID -m '/continue yes'` continues it) |
|
|
40
41
|
| 2 | (no JSON) | usage error, or an option the question doesn't offer |
|
|
41
42
|
| 130 | `running` | Ctrl-C; the turn goes on |
|
|
42
43
|
|
|
@@ -49,8 +50,33 @@ With `--format json` stdout is one JSON object (see
|
|
|
49
50
|
"allow_freeform":false},"answer_with":"chi answer 297da360-… --question 3f1c… --option N"}
|
|
50
51
|
```
|
|
51
52
|
|
|
52
|
-
`kind` is `question` (the model's), `hook`,
|
|
53
|
-
command, folder, rule and reason.
|
|
53
|
+
`kind` is `question` (the model's), `hook`, `approval`, which adds the tool,
|
|
54
|
+
command, folder, rule and reason, or `continue`, the step-limit question below.
|
|
55
|
+
|
|
56
|
+
## The step limit
|
|
57
|
+
|
|
58
|
+
A turn that runs out of steps (`turn.max_iterations`, default 100) before it
|
|
59
|
+
answers waits as a question of kind `continue` (`"limit": 100`, options
|
|
60
|
+
`Continue` and `Stop`). `answer_with` continues it:
|
|
61
|
+
`chi answer ID --question QID --option Continue`, and the continued turn's
|
|
62
|
+
reply (or its next question) comes back on that same wait. To stop it:
|
|
63
|
+
`--option Stop`, with `--text "why"` for the model (Continue takes no text);
|
|
64
|
+
that wait ends with `status: not_continued`, exit 0, and the turn's work so far
|
|
65
|
+
stays in the session. A message instead (`chi send ID -m "…"`) drops the
|
|
66
|
+
question and starts a new turn. It can't be dismissed.
|
|
67
|
+
|
|
68
|
+
A parent may Continue by default: Continue grants no permission (every tool
|
|
69
|
+
call still meets the guardrails), it only spends time and tokens. With
|
|
70
|
+
`turn.parent_continue: false` in chi's config.yml (config.yml only, no
|
|
71
|
+
environment variable) a parent may only Stop: `chi answer` and the worker
|
|
72
|
+
refuse a Continue (exit 1, `a parent agent may only stop this turn here`), and
|
|
73
|
+
the user continues it from the web or the terminal.
|
|
74
|
+
|
|
75
|
+
A chi parent's `delegate_result` gets a child's step-limit question back as a
|
|
76
|
+
`question` with these commands, for its model to decide: continue it (the
|
|
77
|
+
`chi answer` command, through `execute`), send a narrower follow-up with
|
|
78
|
+
`delegate session:` (which drops the question), or report back. It isn't
|
|
79
|
+
relayed to the parent's user the way approvals are.
|
|
54
80
|
|
|
55
81
|
## Approvals
|
|
56
82
|
|
|
@@ -75,6 +101,10 @@ variable), "Allow once" goes through; the wider scopes never do,
|
|
|
75
101
|
and neither does any allow on chi's own config, hooks or guardrail rules. See
|
|
76
102
|
[Guardrails](guardrails.md#approvals-from-a-parent-agent).
|
|
77
103
|
|
|
104
|
+
Start a child in the folder it should change (`chi send --new --dir PATH`):
|
|
105
|
+
writes and mutating git outside its own repo ask (`write-outside-repo`,
|
|
106
|
+
`git-outside-repo`), which a parent can only deny.
|
|
107
|
+
|
|
78
108
|
An answer typed into `chi --attach` or the REPL counts as a parent's too when
|
|
79
109
|
stdin isn't a terminal or an agent marker (`CLAUDECODE`, `AI_AGENT`,
|
|
80
110
|
`CODEX_THREAD_ID`, `SAMAGOTCHI_PARENT_SESSION`) is set.
|
|
@@ -96,7 +126,11 @@ answers from any local process.
|
|
|
96
126
|
`chi answer` then exits 1 and says to send the task again
|
|
97
127
|
(`chi send --wait -m "…" ID`); a wait ends with `worker_gone` (exit 1).
|
|
98
128
|
- A follow-up (`chi send --wait -m "…" ID`) to a session waiting for an answer
|
|
99
|
-
isn't sent: it exits 1 with the `chi answer` command for the question.
|
|
129
|
+
isn't sent: it exits 1 with the `chi answer` command for the question. The
|
|
130
|
+
step-limit question is the exception: it waits between turns, so a message
|
|
131
|
+
goes in, drops it and starts a new turn.
|
|
132
|
+
- `chi send --wait ID` with no message waits past a question already reported
|
|
133
|
+
(the step-limit question too), until someone answers it.
|
|
100
134
|
- An answer to a question that is no longer open (you answered it in the web
|
|
101
135
|
first) isn't sent; `chi answer` says so and waits for what comes next.
|
|
102
136
|
- `chi send --wait` and `chi answer` don't read stdin unless it's a pipe or a
|
|
@@ -130,6 +164,10 @@ Start a task: `chi send --new --wait --format json --timeout 500 -m "<task>"`
|
|
|
130
164
|
`chi answer`, a piped `chi --attach` or chi's web API.
|
|
131
165
|
- `status: running` (exit 4): still working. Wait again:
|
|
132
166
|
`chi send --wait --format json --timeout 500 <session_id>`.
|
|
167
|
+
- `question.kind: continue`: chi ran out of steps before it answered. If it is
|
|
168
|
+
getting somewhere, continue it (`answer_with`, `--option Continue`); if not,
|
|
169
|
+
stop it (`--option Stop --text "why"`, exit 0, status `not_continued`) or send
|
|
170
|
+
a narrower follow-up instead. Ask your user when unsure.
|
|
133
171
|
- Anything else (exit 1): report `detail` to your user. `chi --attach <session_id>`
|
|
134
172
|
shows the session.
|
|
135
173
|
|