@monster-orange/den 0.4.21 → 0.4.23

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/docs/distributions.md +186 -42
  3. package/package.json +1 -1
  4. package/service/install.sh +213 -63
  5. package/src/shared/agents/den-claude.ts +13 -1
  6. package/src/shared/agents/guardrails/rotation-hook.ts +5 -0
  7. package/src/shared/agents/records.ts +6 -0
  8. package/src/shared/agents/rotate-settings.ts +309 -0
  9. package/src/shared/agents/rotation.ts +110 -5
  10. package/src/shared/agents/runtime.ts +6 -0
  11. package/src/shared/agents/session-name.ts +21 -1
  12. package/src/shared/den-config.ts +27 -12
  13. package/src/shared/launchd-session.ts +92 -0
  14. package/src/shared/sessions/client.ts +24 -0
  15. package/src/shared/sessions/host.ts +114 -5
  16. package/src/shared/sessions/types.ts +5 -0
  17. package/src/ui/cli/agent-ops.ts +469 -17
  18. package/src/ui/cli/agents-command.ts +200 -3
  19. package/src/ui/cli/doctor-command.ts +72 -0
  20. package/src/ui/cli/doctor-where.ts +203 -0
  21. package/src/ui/cli/index.ts +1 -1
  22. package/src/ui/cli/server-agent-ops.ts +2 -2
  23. package/src/ui/cli/service-command.ts +217 -11
  24. package/src/ui/cli/sessions-command.ts +46 -12
  25. package/src/ui/cli/web-command.ts +36 -5
  26. package/src/ui/cli/web-service.ts +160 -0
  27. package/src/ui/server/agent-routes.ts +149 -5
  28. package/src/ui/server/index.ts +17 -5
  29. package/src/ui/server/public/agents-page.js +316 -27
  30. package/src/ui/server/public/agents.html +62 -0
  31. package/src/ui/server/public/i18n/es.js +58 -2
  32. package/src/ui/server/public/i18n/ja.js +54 -2
  33. package/src/ui/server/public/settings-registry.js +204 -0
  34. package/src/ui/server/public/settings.html +43 -4
  35. package/src/ui/server/public/shell.js +56 -2
  36. package/src/ui/server/route-classes.ts +21 -0
  37. package/src/ui/server/session-routes.ts +27 -5
package/CHANGELOG.md CHANGED
@@ -4,6 +4,121 @@ All notable changes to Den are documented here. Each release lists the
4
4
  config and manifest changes a distribution must make to move to it under
5
5
  **Migrations**.
6
6
 
7
+ ## [0.4.23] - 2026-11-10
8
+
9
+ ### Added
10
+
11
+ - **Agents relaunch from their handoff by themselves (D28).** When an
12
+ agent's rotation is due (after `after_compactions` compactions, or its
13
+ pull request merged), Den asks it for its handoff at its next idle,
14
+ never mid-turn, and relaunches it once the handoff passes: a fresh
15
+ conversation with the same name, persona, workspace and folder. A toast
16
+ and the audit log say so, and the old conversation is kept. Den asks
17
+ twice at most, then falls back to asking you.
18
+ - **The rotation callout has buttons, running or stopped**, on the row and
19
+ in the pane bar: Relaunch now (asking for the handoff first if needed),
20
+ Write handoff, Keep this conversation (with "Don't ask again for this
21
+ agent") and Settings. It says what happens next and where the settings
22
+ come from. Before, a running agent got a message and nothing to press,
23
+ and Stop then Start showed the same message again.
24
+ - **Rotation settings at every scope.** `after_compactions` (a number or
25
+ `never`), `on_due` (`auto`, the default, `ask` or `off`) and
26
+ `on_pr_merged`. The most specific wins: agent, persona in a workspace,
27
+ persona, workspace, service, this install, org config. Settings › Agents
28
+ › Rotation and `<cli> agents rotate` set them; `<cli> agents rotate <id>
29
+ --show` names where each value comes from. `<cli> agents keep` and
30
+ `<cli> agents handoff` are the callout's buttons. A change applies to
31
+ running agents too.
32
+ - Resume (or Start) on a due agent follows `on_due`: `auto` relaunches it
33
+ from its handoff, `ask` refuses with the callout's choices, `off` goes
34
+ on with the same conversation.
35
+
36
+ ### Fixed
37
+
38
+ - **`web restart` with the service installed** restarts the launchd job
39
+ and names its pid and the service's log. Before, it started a plain
40
+ server, said "started", and that server died seconds later because
41
+ launchd had brought the job back onto the port. `web up` starts a job
42
+ that isn't answering; `web down` refuses and names `service uninstall`.
43
+ - The "port in use" message no longer says to kill whatever holds the
44
+ port, which killed the service. It names `service status` and `web
45
+ restart`.
46
+
47
+ ### Migrations
48
+
49
+ - None required. Rotation now relaunches by itself (`on_due: auto`). To
50
+ keep den#4's behavior (a person relaunches), set `on_due: ask` under
51
+ `agents.rotate` in the org config, or `<cli> agents rotate on_due ask`.
52
+ `agents.rotate.standing` still works; a persona override replaces it.
53
+
54
+ ## [0.4.22] - 2026-11-10
55
+
56
+ ### Added
57
+
58
+ - **Opt-in: agents meant to keep running across logout (D18, D20).**
59
+ `service install --mode always` installs the web server and the session
60
+ host as LaunchDaemons that run as you (`UserName`): they start at boot
61
+ and are meant to keep running when you log out of the desktop, and so
62
+ every terminal and agent. It is not yet proven across a logout: `service
63
+ help`, `--plan`, install, `doctor` and the agents page say so. It takes your admin password once (sudo); `--mode login`, the
64
+ default, keeps LaunchAgents in the desktop session (they stop at
65
+ logout). Without `--mode`, install keeps the mode already installed.
66
+ Switching removes the other mode's jobs.
67
+ - **The session host is its own launchd job** (`<label>` with `.web` →
68
+ `.sessions`), in both modes, so it runs in the mode's session whoever
69
+ starts the first terminal: before, it inherited the launchd session of
70
+ whatever started it, and one started from the desktop ended at logout
71
+ with every agent in it. `<cli> sessions` and agents wait for the job.
72
+ - **Full Disk Access for Den, not for every node program.** The host job
73
+ runs a copy of node Den owns, `<stateDir>/bin/<cli>-sessions-host`
74
+ (0700), the path to grant Full Disk Access to (protected folders: other
75
+ volumes, Desktop, Documents); agents inherit it. Install refreshes the
76
+ copy when node changes and says when the grant must be given again (a
77
+ new signer, an ad-hoc signed node). A node that can't be copied
78
+ (Homebrew's) is run directly, and install says so.
79
+ - **Where it runs, shown honestly.** Every session records the launchd
80
+ session its host ran in (`launchctl managername`). The Agents page tags
81
+ each agent **Stops at logout** (the desktop session; tap for the fix) or
82
+ **No GUI** (kept across logout: no login keychain, no GUI apps; 1Password
83
+ through the service-account token, headless browsers, Full Disk Access
84
+ for protected folders). `doctor` ends with **where it runs**: the
85
+ server's and the host's session, Full Disk Access and the path to grant,
86
+ and the service's mode. `web up` from an ssh (Background) shell says what
87
+ a server started there means.
88
+ - **Rename, visible (D25).** **Rename…** in an agent pane's ⋯ menu and
89
+ **Rename** on a stopped agent's row, by touch or keyboard, and
90
+ `<cli> agents rename <id> <name>` (`--clear`: back to its id). Before,
91
+ only a double-click on an open tab renamed one.
92
+ - **Agents tell apart in a list of sessions (D24).** Den names each
93
+ Claude Code agent's session `<cli>-<ws>:<name, else id>` (`--name`) at
94
+ start and resume, so another session listing its peers sees which agent
95
+ is which, not a generic name. A rename shows at its next start or resume.
96
+
97
+ ### Fixed
98
+
99
+ - **`service install` said running while its job crash-looped (D26).** A
100
+ plain `web up` server of the same install on the port answered the
101
+ check, while the job failed to start on the taken port. Install now
102
+ stops a plain server of this install first, and says running only when
103
+ the server answering is the job's own process; `service status` says
104
+ when another process holds the port. `--plan` says when install would
105
+ stop one.
106
+ - **`service status` names a job whose plist is present but not loaded**,
107
+ with the `launchctl bootstrap` that loads it (`sudo` for a LaunchDaemon);
108
+ `doctor` shows it too.
109
+ - **Tests never reach this machine's launchctl, gh or vendor CLIs**, even
110
+ through a spawn without `env`: the inert stand-ins are now first on the
111
+ real PATH (jest's globalSetup), not only on each test file's sandboxed
112
+ `process.env`.
113
+
114
+ ### Migrations
115
+
116
+ - None required. An existing install keeps its mode (login). To try
117
+ keeping agents running across logout (not yet proven), run
118
+ `<cli> service install --mode always` once (one sudo), then grant Full
119
+ Disk Access to the path `doctor` names if agents read protected folders.
120
+ - `<cli> service` with no subcommand prints its help (before, an error).
121
+
7
122
  ## [0.4.21] - 2026-11-09
8
123
 
9
124
  ### Added
@@ -1957,16 +1957,96 @@ Apps › New GitHub App):
1957
1957
 
1958
1958
  ## Running the web UI as a service (macOS)
1959
1959
 
1960
- Den ships a launchd installer that runs the web UI at login and restarts it if
1961
- it exits:
1960
+ Den ships a launchd installer that runs the web UI and the session host (every
1961
+ terminal and agent) as launchd jobs, restarted if they exit:
1962
1962
 
1963
1963
  ```bash
1964
- ./bin/acme service install # install (or refresh) and start it
1964
+ ./bin/acme service install # install (or refresh) and start them
1965
+ ./bin/acme service install --mode always # meant to keep them running across logout (opt-in, not yet proven)
1965
1966
  ./bin/acme service install --plan # what install would do; writes and loads nothing
1966
1967
  ./bin/acme service status # launchd's view: state, pid, last exit code
1967
- ./bin/acme service print # the LaunchAgent plist it would install; installs nothing
1968
- ./bin/acme service uninstall # stop it and remove the LaunchAgent
1969
- ```
1968
+ ./bin/acme service print # the web plist it would install; installs nothing
1969
+ ./bin/acme service uninstall # stop them and remove their plists
1970
+ ```
1971
+
1972
+ With the service installed, `web` works through launchd. `web restart`
1973
+ restarts the job (`launchctl kickstart -k`) and names its new pid and the
1974
+ service's log. `web up` starts the job when it isn't answering. `web down`
1975
+ refuses, because launchd would only start it again: use `service
1976
+ uninstall` to stop it. Under `always`, restarting needs sudo, so `web
1977
+ restart` prints the `sudo launchctl kickstart -k system/<label>` line for
1978
+ you to run.
1979
+
1980
+ ### Login or always
1981
+
1982
+ | `--mode` | Jobs | At logout |
1983
+ | ----------------- | ----------------------------------------------------- | ------------------------------------ |
1984
+ | `login` (default) | LaunchAgents in the desktop session (`gui/<uid>`) | stop, with every agent |
1985
+ | `always` | LaunchDaemons that run as you (`UserName`), from boot | meant to keep running, and so agents |
1986
+
1987
+ `always` is opt-in and not yet proven across a logout: no logout has
1988
+ shown it keeping an agent on the same conversation yet. Use `login` unless
1989
+ you are testing it.
1990
+
1991
+ `always` takes your admin password once (sudo writes and loads the
1992
+ LaunchDaemons in `/Library/LaunchDaemons`); it is never asked for again.
1993
+ Without `--mode`, install keeps the mode already installed, so re-running it
1994
+ after an upgrade never changes it. Switching mode removes the other mode's
1995
+ jobs: never both. Use `always` on a machine you reach from elsewhere (ssh, a
1996
+ browser on another machine); `login` on a laptop.
1997
+
1998
+ A process outside the desktop session has no login keychain and no GUI. So
1999
+ under `always`, agents use:
2000
+
2001
+ - **1Password**: the service-account token (`secrets.onepassword.token`),
2002
+ not the desktop app. `doctor` warns when this process has none.
2003
+ - **Browsers**: headless Chromium and Firefox. WebKit on macOS needs a
2004
+ desktop session; run it from Playwright's Linux image instead
2005
+ (CONTRIBUTING.md, `DEN_PW_WS`).
2006
+ - **Protected folders** (other volumes, Desktop, Documents): Full Disk
2007
+ Access, granted once (below).
2008
+
2009
+ Each session records the launchd session its host ran in, and the Agents page
2010
+ tags it: **Stops at logout** (the desktop session; tap it for the fix) or
2011
+ **No GUI** (kept across logout). `web up` from an ssh shell (a Background
2012
+ session) says the same before it starts a server there.
2013
+
2014
+ ### The session host job and Full Disk Access
2015
+
2016
+ Both modes install the session host as its own job, `<label>` with `.web`
2017
+ replaced by `.sessions` (`local.acme.sessions`), so it runs in the mode's
2018
+ session whoever starts the first terminal; `<cli> sessions` and agents then
2019
+ wait for it rather than starting a host in the caller's session. It runs a
2020
+ copy of node Den owns, `<stateDir>/bin/<cli>-sessions-host` (0700), and
2021
+ macOS grants Full Disk Access to that path: never to the shared node, which
2022
+ would give it to every node program. To grant it: System Settings → Privacy &
2023
+ Security → Full Disk Access → + → that path (⌘⇧G to type it), then
2024
+ `<cli> sessions host --stop` (it restarts with the grant). Agents inherit it.
2025
+
2026
+ Install refreshes the copy when node changes. An official node build
2027
+ (nodejs.org, nvm) is signed by the Node.js project, so the grant holds across
2028
+ upgrades; install says when the signer changed and it must be granted again.
2029
+ An ad-hoc signed node needs a new grant after every change, and Homebrew's
2030
+ node loads libraries beside it and can't be copied: install then runs it
2031
+ directly and says so. `doctor` shows whether the host has Full Disk Access,
2032
+ and the path to grant.
2033
+
2034
+ ### What doctor shows
2035
+
2036
+ `doctor` ends with **where it runs**: the web server's and the session host's
2037
+ launchd session (a warning with the fix when it stops at logout), Full Disk
2038
+ Access, and the service: its mode, or `plist present, job not loaded:
2039
+ launchctl bootstrap <domain> <plist>` (`sudo` for a LaunchDaemon) when launchd
2040
+ has dropped a job whose plist is still there.
2041
+
2042
+ ### Running means the job's own process
2043
+
2044
+ A plain `web up` server of the same install on the port would answer for the
2045
+ job, which then fails to start on the taken port and restarts forever. So
2046
+ install stops a plain server of this install first (and says so), and says
2047
+ `running` only when the server answering is the job's own process (its pid is
2048
+ launchd's). Otherwise it says `port <N> is served by pid <P>, not the job`
2049
+ and exits 1; `status` says the same.
1970
2050
 
1971
2051
  The label and log directory come from the org config:
1972
2052
 
@@ -2011,12 +2091,14 @@ node_modules/@monster-orange/den/service/install.sh uninstall
2011
2091
  node_modules/@monster-orange/den/service/install.sh print # the plist only
2012
2092
  ```
2013
2093
 
2014
- | Variable | Default |
2015
- | ------------------- | ---------------------------------------- |
2016
- | `DEN_ROOT` | required in a distribution |
2017
- | `DEN_SERVICE_LABEL` | `local.<distribution dir name>.web` |
2018
- | `DEN_SERVICE_LOGS` | `~/Library/Logs/<distribution dir name>` |
2019
- | `DEN_SERVICE_NODE` | see below |
2094
+ | Variable | Default |
2095
+ | ---------------------- | ----------------------------------------------------------------------- |
2096
+ | `DEN_ROOT` | required in a distribution |
2097
+ | `DEN_SERVICE_LABEL` | `local.<distribution dir name>.web` |
2098
+ | `DEN_SERVICE_LOGS` | `~/Library/Logs/<distribution dir name>` |
2099
+ | `DEN_SERVICE_NODE` | see below |
2100
+ | `DEN_SERVICE_MODE` | `login` (or `always`) |
2101
+ | `DEN_SERVICE_HOST_EXE` | none: the host runs that node (`<cli> service install` sets Den's copy) |
2020
2102
 
2021
2103
  The service runs the engine's server, from the distribution root, with the
2022
2104
  `node` on your `PATH` at install time. A version manager's node (nvm, fnm,
@@ -2495,6 +2577,15 @@ server scoped to its workspace.
2495
2577
  start and resume answers). 0.4.12 sent the name shown as `sessionName`;
2496
2578
  read `sessionTitle` instead. Records are unchanged, so none written
2497
2579
  before needs migrating.
2580
+ - **Renaming.** `agents rename <id> <name>` (or `--clear`, back to its
2581
+ id) names an agent; so do **Rename…** in its pane's ⋯ menu and **Rename**
2582
+ on a stopped agent's row, by touch or keyboard, as well as a double-click
2583
+ on its tab. The id never changes.
2584
+ - **The name other sessions see.** Den names a Claude Code agent's session
2585
+ `<cli>-<ws>:<name, else id>` (`--name`) at every start and resume, so
2586
+ another session listing its peers tells Den's agents apart. A rename
2587
+ shows at its next start or resume. The record's `peerName` is the name
2588
+ last given, so reading it back is never taken for a `/rename`.
2498
2589
  - `list` shows agents only: sessions with an agent record. A session from
2499
2590
  `sessions start` is not an agent; `sessions list` shows it.
2500
2591
  - `stop` sends its session TERM and marks it stopped. `resume` works only
@@ -3213,10 +3304,12 @@ report. It works on den-claude for personas with Den's hooks on.
3213
3304
  the turn once while the handoff is missing or fails the check. The
3214
3305
  instruction: write `handoff.md` in the agent's files folder, then end
3215
3306
  the turn.
3216
- 4. Den relaunches the role. **Relaunch from handoff** starts a fresh
3217
- conversation (a new conversation id) with the same persona, workspace,
3218
- folder and grants. Its first prompt is the handoff. The handoff moves to
3219
- `agent-files/<id>/handoffs/`, and the record notes the rotation.
3307
+ 4. Den relaunches the role, by itself unless the settings say otherwise
3308
+ (`on_due`, below). A relaunch starts a fresh conversation (a new
3309
+ conversation id) with the same name, persona, workspace, folder and
3310
+ grants. Its first prompt is the handoff. The handoff moves to
3311
+ `agent-files/<id>/handoffs/`, the record notes the rotation, and the
3312
+ old conversation is kept.
3220
3313
 
3221
3314
  **The handoff** has five parts, each a heading, each filled in. Write it
3222
3315
  plainly, and explain every ID, as in a hand-back. It must stay under 20,000
@@ -3249,38 +3342,89 @@ agent's `handoff.md` (by its id), a file, or stdin: exit 0 when
3249
3342
  it passes, 1 with each missing or empty part and each unexplained ID. A
3250
3343
  part can also be a line such as `Next step: open the PR`.
3251
3344
 
3252
- **Resume after a rotation.** Once a rotation is due and `handoff.md`
3253
- passes, Den declines a plain Resume of that conversation:
3254
-
3255
- - The Agents page shows the row as "Ready to relaunch (3 compactions)" or
3256
- "Ready to relaunch (PR merged)". Its buttons are Relaunch from handoff
3257
- (the primary one), Start fresh and Remove.
3258
- - `<cli> agents resume <id>` refuses (exit 1) and prints
3259
- `Relaunch from handoff: <cli> agents relaunch <id>`. The resume route
3260
- answers 409 with code `rotated`.
3261
- - `<cli> agents relaunch <id> [-w <ws>]` and
3262
- `POST /api/workspaces/<ws>/agents/<id>/relaunch` relaunch it. A running
3263
- rotated agent is stopped first.
3264
- - With `handoff.md` missing or failing the check, the row says so, and
3265
- Resume and Start fresh stay. Resume the agent to write or fix it.
3266
- Relaunch says what is wrong (409 `no_handoff`) and names Start fresh.
3267
- - After a restart, a standing persona that rotated relaunches on its own,
3268
- as a standing persona resumes on its own.
3269
-
3270
- **Settings**, in the org config:
3345
+ **What happens when a rotation is due** (`on_due`):
3346
+
3347
+ - `auto` (the default). At the agent's next idle, never mid-turn, Den
3348
+ types the instruction into its session: write the handoff. Once the
3349
+ handoff passes the check and the agent is idle, Den relaunches it. The
3350
+ page shows a toast ("Relaunched <id> from its handoff after 3
3351
+ compactions. The old conversation <id> is kept.") and the audit log
3352
+ records it. Den asks at most twice, two minutes apart. If the handoff
3353
+ still fails, it falls back to `ask`.
3354
+ - `ask`. Nothing happens by itself. The agent's callout has the buttons.
3355
+ - `off`. Nothing happens, and no callout shows. Resume goes on with the
3356
+ same conversation.
3357
+
3358
+ **The callout** shows on a due agent's row (running or stopped) and in its
3359
+ pane bar. It says what happens next and where the settings come from
3360
+ ("Rotates after 3 compactions (persona planner); when due: relaunch by
3361
+ itself (default)"). Its buttons:
3362
+
3363
+ - **Relaunch now**: from the handoff when it passes. Otherwise Den asks
3364
+ for it first (a stopped agent resumes to write it) and relaunches it
3365
+ once it passes, whatever `on_due` says.
3366
+ - **Write handoff**: on a running agent without one, types the
3367
+ instruction. It is refused mid-turn.
3368
+ - **Keep this conversation**: the compaction count starts again. With
3369
+ "Don't ask again for this agent", that agent's `after_compactions` is
3370
+ set to `never`. A person's only.
3371
+ - **Settings**: Settings › Agents › Rotation.
3372
+
3373
+ **Resume and Start on a due agent** follow `on_due`. Under `auto`, Resume
3374
+ relaunches it from its handoff once the handoff passes. Under `ask`,
3375
+ `<cli> agents resume <id>` refuses (exit 1) and names
3376
+ `<cli> agents relaunch <id>`, and the resume route answers 409 with code
3377
+ `rotated`. `<cli> agents relaunch <id> [-w <ws>]` and
3378
+ `POST /api/workspaces/<ws>/agents/<id>/relaunch` relaunch it at any time
3379
+ (a running agent is stopped first; a missing or failing handoff is a 409
3380
+ `no_handoff`). After a restart, a standing persona that rotated relaunches
3381
+ on its own.
3382
+
3383
+ **Settings.** Three keys:
3384
+
3385
+ | Key | Values | Default |
3386
+ | ------------------- | ---------------------- | ------- |
3387
+ | `after_compactions` | 1 to 100, or `never` | 3 |
3388
+ | `on_due` | `auto`, `ask` or `off` | `auto` |
3389
+ | `on_pr_merged` | `true` or `false` | `true` |
3390
+
3391
+ Each key comes from the most specific scope that sets it: the agent, the
3392
+ persona in a workspace, the persona, the workspace, the service (the
3393
+ workspace service whose folder holds the agent's folder), this install,
3394
+ the org config, then Den's default. A change applies to running agents
3395
+ too, at their next look (about every 15 seconds).
3396
+
3397
+ The org's values go in the org config:
3271
3398
 
3272
3399
  ```yaml
3273
3400
  agents:
3274
3401
  rotate:
3275
- after_compactions: 3 # 1 to 100; the default is 3
3276
- standing: true # standing personas rotate too (the default)
3402
+ after_compactions: 3 # 1 to 100, or never
3403
+ on_due: auto # auto, ask or off
3404
+ on_pr_merged: true
3277
3405
  ```
3278
3406
 
3279
- `standing` is for standing personas, such as the planner and the
3280
- orchestrator. With `true` (the default), they rotate like the rest: only
3281
- their context resets, and they keep their id and workspace. With `false`,
3282
- they never rotate. A setting takes effect at the agent's next start or
3283
- relaunch. An unknown key, or a bad value, is an error naming the key.
3407
+ This install's values and every override are set in Settings › Agents ›
3408
+ Rotation, or from the command line, and kept in
3409
+ `<stateDir>/agent-rotation.json`:
3410
+
3411
+ ```bash
3412
+ <cli> agents rotate # every value and override
3413
+ <cli> agents rotate on_due ask # this install
3414
+ <cli> agents rotate --persona planner after_compactions never
3415
+ <cli> agents rotate --persona planner -w notes after_compactions 5 # that persona in notes
3416
+ <cli> agents rotate --workspace notes on_due off
3417
+ <cli> agents rotate --service shop on_pr_merged false
3418
+ <cli> agents rotate --agent art --unset after_compactions
3419
+ <cli> agents rotate art --show # its values, and where each comes from
3420
+ <cli> agents keep <id> [--never] # Keep this conversation
3421
+ <cli> agents handoff <id> # Write handoff
3422
+ ```
3423
+
3424
+ `agents rotate` and `agents keep` are refused inside an agent's session.
3425
+ den#4's `standing: false` still works: standing personas never rotate,
3426
+ unless a persona or agent override says otherwise. Set it per persona
3427
+ instead (`--persona planner after_compactions never`).
3284
3428
 
3285
3429
  ### Runtimes
3286
3430
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monster-orange/den",
3
- "version": "0.4.21",
3
+ "version": "0.4.23",
4
4
  "description": "Den: a local multi-repo dev orchestrator, HTTPS edge and E2E harness",
5
5
  "type": "module",
6
6
  "bin": {