@assuredloop/orca-bot-kit 0.6.0 → 0.8.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.
package/README.md CHANGED
@@ -340,7 +340,7 @@ with why that one ended and when
340
340
 
341
341
  The book stays true to the harness through a hook `obk up` puts in the bot's own
342
342
  folder — `.claude/settings.json` or `.codex/hooks.json`, never your user-level
343
- settings ([ADR 0020](docs/adr/0020-kit-hooks-live-in-the-bot-folder.md)). Your own
343
+ settings ([ADR 0022](docs/adr/0022-kit-hooks-live-in-the-bot-folder.md)). Your own
344
344
  settings in those files are kept. Codex asks you to trust a hooks file the first
345
345
  time it sees one; answer its question in the tab.
346
346
 
@@ -431,6 +431,31 @@ retiring it again finishes the job. Bot
431
431
  Father itself, and its management session `daily`, are never paused or
432
432
  retired; its other sessions are like any bot's.
433
433
 
434
+ ## Daily grooming
435
+
436
+ Grooming is an optional daily pass over the fleet: what each bot did, what it
437
+ cost, and what to improve, sent as one short report to Bot Father's management
438
+ session. It runs in a session of Bot Father's called `grooming`, on Claude
439
+ Code's own scheduler, at the model and effort you give that session.
440
+
441
+ ```sh
442
+ obk session add --bots /path/to/my-bots --bot bot-father --name grooming --model sonnet --effort medium
443
+ obk up --bots /path/to/my-bots --bot bot-father
444
+ obk groom --bots /path/to/my-bots --now # run it once, by hand, and read it
445
+ obk groom --bots /path/to/my-bots --on --at 04:00 # then schedule it
446
+ obk groom --bots /path/to/my-bots # what is scheduled
447
+ ```
448
+
449
+ It starts off, and Bot Father asks you before it turns it on, because it spends
450
+ tokens every day. It fires only while its tab is open in Orca and Claude Code is
451
+ idle there, up to half an hour after its time, and a day it misses is not made
452
+ up. Each run renews its own schedule, which Claude Code would otherwise end
453
+ after a week, and `--on --at` puts it back if it lapses. A `/clear` in its tab
454
+ leaves it running until the session is next restarted; after that, `--on --at`
455
+ puts it back.
456
+ `obk groom --compact` compacts its conversation between runs. Grooming on Codex
457
+ comes later: on a Codex Bot Father, add the session with `--harness claude`.
458
+
434
459
  ## When something is wrong
435
460
 
436
461
  ```sh
@@ -545,6 +570,7 @@ failing quietly.
545
570
  npm test # the whole suite, in a few minutes
546
571
  npm run test:system # what the system tests would drive, and nothing else
547
572
  npm run test:system -- --yes # drive them, on this machine, for real
573
+ npm run test:system -- --yes test/system/groom.test.js # only the ones you name
548
574
  npm run mutate # the mutation audit, on what your branch changed
549
575
  ```
550
576
 
@@ -570,7 +596,9 @@ which Orca, which files, and what they will do to it — and then stops without
570
596
  driving any of it, answering non-zero so that a run which did not run them
571
597
  cannot be read as one that passed. `-- --yes` is how you say you meant it. When
572
598
  Orca is not answering, the command says which Orca it asked and skips, rather
573
- than report a kit that is not broken.
599
+ than report a kit that is not broken. Name files after it to run only those;
600
+ each must be one of the `*.test.js` files under `test/system/`, links followed,
601
+ and a name that is not is refused before anything runs.
574
602
 
575
603
  `npm run mutate` runs [StrykerJS](https://stryker-mutator.io) over the
576
604
  JavaScript this branch changed against `main` — committed, still in the working
package/SETUP.md CHANGED
@@ -102,8 +102,9 @@ Ask both in one message, then wait.
102
102
 
103
103
  **Where the bots folder goes.** An absolute path. It becomes a git repository of
104
104
  theirs holding every bot's configuration: charters, rules, session settings, the
105
- book of sessions. It holds none of the kit's code. They may push it somewhere
106
- later; the kit never commits for them.
105
+ book of sessions. It holds none of the kit's code. The bots commit their own
106
+ changes to it; the kit's commands never commit, and pushing it somewhere is
107
+ theirs to decide.
107
108
 
108
109
  **Which harness Bot Father runs on**, `claude` or `codex`. There is no default
109
110
  and the kit will not guess. It is Bot Father's own harness only; each bot they
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assuredloop/orca-bot-kit",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: commit
3
+ title: Commit your changes
4
+ applies: all
5
+ ---
6
+
7
+ Commit your own changes to the bots repo often, as each piece of work there is
8
+ done: what you changed, in whatever folder it is, and what the kit wrote in
9
+ your own bot's folder (its files there and your book of sessions). Stage each
10
+ file by name and name them in the commit too (`git commit -- <files>`), so
11
+ nothing another session staged goes in with yours. What no bot owns (init's
12
+ files, the defaults, the user's rules and skill lists) is Bot Father's to
13
+ commit. Commit yours even when the repo has no commit yet or holds files that
14
+ are not yours; leave those to their owner.
15
+
16
+ Committing is not pushing: pushing is the user's call. A clone in your work dir
17
+ follows its own repo's rules. When a commit fails, for example on another
18
+ session's index lock, try again, and say so if it still fails.
@@ -214,8 +214,12 @@ index load at every start. Codex's memories belong to the user and are shared
214
214
  by every Codex session, so a Codex bot cannot have its own; say so before
215
215
  turning them on for one. It is a hand edit, and say that you made it.
216
216
 
217
- After each change, commit what it changed in the bots folder; the kit never
218
- commits by itself.
217
+ After each change, commit what it changed in the bots folder, naming each
218
+ file when you stage it and again in the commit. Commit with it what no bot
219
+ owns and nobody has committed yet: `init`'s files, `defaults.yaml`,
220
+ `skills.yaml`, and the user's `rules/` and `skills/`. What it changed
221
+ includes the folder of a bot it made or changed. The kit never commits by
222
+ itself.
219
223
 
220
224
  Do the reversible part and stop at the line. Making a bot, adding a session
221
225
  and linking skills add things and are safe to run again; closing a tab,
@@ -251,6 +251,7 @@ identifiers, counts and paths that only show the work; where evidence matters,
251
251
  say where it is.
252
252
 
253
253
  Say what you did not look at. Where you changed anything in the bots folder,
254
- commit it; the kit never commits by itself.
254
+ commit it, naming each file when you stage it and again in the commit; the
255
+ kit never commits by itself.
255
256
 
256
257
  Sources and licences: [NOTICE.md](NOTICE.md).
@@ -50,11 +50,13 @@ profile notes and that any bot may read them; and the routing, where something
50
50
  the kit does wrong is filed as an issue in one step and something a bot does
51
51
  wrong goes back to that bot's session as feedback.
52
52
 
53
- That grooming is the automation's own session rather than a tab in the book,
54
- and that what it must remember therefore lives in files, follows from a fact
55
- measured for this slice: an Orca automation cannot reuse a session it did not
56
- start, so it cannot be pointed at a tab the kit made. It is written down in
57
- the tech notes.
53
+ Grooming runs in a session in the book, on Claude Code's own scheduler,
54
+ because the owner decided on 2026-09-24 (#223) that scheduled work does not use
55
+ an Orca automation: an automation cannot carry a model or an effort of its
56
+ own. Its runs share one long conversation, which the owner accepted in the
57
+ same decision ("just do /compact when needed, add to the skill"). What a run
58
+ must remember still lives in files, because a compacted conversation keeps
59
+ only a summary. The facts about Claude Code's scheduler are in the tech notes.
58
60
 
59
61
  ## Ideas taken from material with no licence
60
62
 
@@ -109,3 +111,17 @@ Sources and licences in full: [LICENSES.md](LICENSES.md).
109
111
  expected). The load limit and where the memory lives are the tech notes'
110
112
  facts, verified live on Claude Code 2.1.280; Codex's store is the user's and
111
113
  is left alone.
114
+
115
+ ## What moving to Claude Code's scheduler changed (#237)
116
+
117
+ - Where it remembers: the runs share one conversation now, so the files are
118
+ kept because a compact leaves a summary and a `/clear` leaves nothing, rather
119
+ than because each run starts fresh.
120
+ - Turning it on: a `grooming` session in the book with its own model and
121
+ effort, a run by hand with `obk groom --now`, then `--on --at` after the yes.
122
+ That it fires only while its tab is up, the week-long limit each run renews,
123
+ and what a `/clear` does are Claude Code's documented behaviour, in the tech
124
+ notes.
125
+ - Compacting is new, from the owner's decision on #223. Who types `/compact`
126
+ (the management session, through the kit) is the architect's reading of it on
127
+ #237.
@@ -53,17 +53,20 @@ say what was and was not looked at rather than taking the report's word for it.
53
53
 
54
54
  ## Where it remembers
55
55
 
56
- Each run is a fresh conversation, so everything it knows from the last one is
57
- in files in the bots folder: the window and the open findings in a `grooming/`
58
- folder unless the project already keeps them elsewhere, and the profile notes
59
- in `profiles/`, where the fleet's rules tell every bot to look. Say at the top
60
- of each what it is. A run that cannot find
56
+ The runs share one long conversation, and a compacted conversation keeps a
57
+ summary of the earlier runs, not their detail; a `/clear` keeps nothing. So
58
+ everything a run needs from the last one is in files in the bots folder: the
59
+ window and the open findings in a `grooming/` folder unless the project already
60
+ keeps them elsewhere, and the profile notes in `profiles/`, where the fleet's
61
+ rules tell every bot to look. Say at the top of each what it is. Trust the
62
+ files over what the conversation seems to remember. A run that cannot find
61
63
  them says so and reads less, not everything.
62
64
 
63
65
  The open findings are what stops the same thing being reported every day:
64
66
  before filing an issue or sending feedback, look there, and where it is already
65
67
  open, add to it rather than raising it again. Take a finding off when it is
66
- fixed or no longer true. Commit these once a day, at the end of the run.
68
+ fixed or no longer true. Commit these at the end of each run, naming each
69
+ file when you stage it and again in the commit.
67
70
 
68
71
  ## Keep the run bounded
69
72
 
@@ -212,11 +215,45 @@ places and sending one to the other loses it.
212
215
 
213
216
  ## Turning it on
214
217
 
215
- Do not schedule a pass nobody has watched. Run it by hand once, read what it
216
- produced, and fix the run rather than the report. So `obk groom --at <HH:MM>`
217
- creates it off, and `obk groom --on` turns it on after one explicit yes from
218
- the user who has read a run. Tell them what it will cost them each day before
219
- they say it. Make it through that command rather than by hand, so there is only
220
- ever one.
218
+ The pass runs in a session of Bot Father's own, `grooming`, on Claude Code's
219
+ own scheduler. Add it like any session, with the model and effort it is to run
220
+ at: `obk session add --bots <folder> --bot bot-father --name grooming --model
221
+ <m> --effort <e>`, with `--harness claude` where Bot Father runs on Codex, then
222
+ `obk up`. Its launch line carries them, so they are what every run uses.
223
+
224
+ Do not schedule a pass nobody has watched. `obk groom --now` runs it once in its
225
+ tab; read what it produced, and fix the run rather than the report. Then, after
226
+ one explicit yes from the user who has read a run, `obk groom --on --at <HH:MM>`
227
+ schedules it. Tell them what it will cost them each day before they say it.
228
+ Schedule it through that command rather than by hand, so there is one job and
229
+ the kit can see it. `obk groom` says what is scheduled, and says so plainly
230
+ when there is none or more than one.
231
+
232
+ It fires only while its tab is open in Orca and Claude Code is idle in it, up to
233
+ half an hour after its time, and a day it misses is not made up. Tell the user
234
+ that before they rely on it. Claude Code ends a recurring job a week after it
235
+ was made, so each run renews its own; a week with no run ends it, and then
236
+ `obk groom --on --at <HH:MM>` puts back what the user agreed to. A `/clear` in
237
+ its tab leaves it running: the job belongs to the running Claude Code, not to
238
+ one conversation. It keeps running until the session is next restarted or
239
+ Orca restores its tab: a resume brings back only the jobs of the conversation
240
+ it resumes, so after a `/clear` and then a restart the job is gone. `obk groom`
241
+ says so, and `--on --at` puts it back.
242
+
243
+ ## Compacting
244
+
245
+ The runs share one conversation and each adds to it, so each run carries more
246
+ than the last and costs more. A compact shrinks it to a summary. The run cannot
247
+ do it: a scheduled run cannot type a built-in command, and a session cannot
248
+ compact itself. So the report says when one is due, and the management session
249
+ does it between runs with `obk groom --compact`.
250
+
251
+ One is due when the run's calls carry about half the model's context window.
252
+ `obk usage --bots <folder> --bot bot-father --session grooming --since <this
253
+ run's start>` gives the run's calls and tokens: input, cache read and cache
254
+ write together, divided by the calls, is roughly what each call carried.
255
+ Claude Code also compacts on its own near the limit. That is the backstop, not
256
+ the plan: it comes when the conversation is full, most likely in the middle of
257
+ a run.
221
258
 
222
259
  Sources and licences: [NOTICE.md](NOTICE.md).
package/src/book.js CHANGED
@@ -122,9 +122,7 @@ export async function updateBook(home, change) {
122
122
  /**
123
123
  * The right to change what belongs to one bot, held from before it is read until
124
124
  * after it is written — a SQLite write transaction on a file of its own beside
125
- * the book. The book is what it usually guards, and `obk groom` takes it too,
126
- * for the one thing Orca will not do for us: look for an automation and make one
127
- * if it is missing, without a second run doing the same between the two.
125
+ * the book.
128
126
  *
129
127
  * SQLite is in Node itself and does this with the operating system's own file
130
128
  * locks. That matters for one reason: the lock belongs to the process, so it is
@@ -142,7 +140,7 @@ export async function updateBook(home, change) {
142
140
  * The lock file holds no data — it is opened, locked and closed — so it stays
143
141
  * empty and leaves no journal beside it.
144
142
  */
145
- export function takeLock(home) {
143
+ function takeLock(home) {
146
144
  const file = lockFile(home);
147
145
  mkdirSync(path.dirname(file), { recursive: true });
148
146
 
package/src/bot.js CHANGED
@@ -59,7 +59,7 @@ export function botNames(bots) {
59
59
  * link the user made takes it out of that folder; undefined when it stays in.
60
60
  *
61
61
  * The kit writes only in the bot folder, never in user-level settings (PRD 6.3,
62
- * ADR 0020), and a link would make the one the other without a word. So the
62
+ * ADR 0022), and a link would make the one the other without a word. So the
63
63
  * answer is the file system's, not the spelling's: see `whereItLeads`.
64
64
  */
65
65
  export function leadsOutside(home, target) {
package/src/cli.js CHANGED
@@ -13,7 +13,7 @@ import { fileURLToPath } from 'node:url';
13
13
  import { parseArgs } from 'node:util';
14
14
 
15
15
  import { addSession, changeBot, changeSession, createBot, leadsOutside, readBot, SESSION_FIELDS } from './bot.js';
16
- import { grooming } from './groom.js';
16
+ import { addCommand, groomCommand, grooming, upCommand } from './groom.js';
17
17
  import { checkHealth, orcaSettingFindings } from './health.js';
18
18
  import { initBots } from './init.js';
19
19
  import { APPROVALS, HARNESSES, ownCli, shellWord, workDirOf } from './launch.js';
@@ -164,13 +164,18 @@ Usage:
164
164
  with its settings, its tab and the conversation it
165
165
  is in. It reads your files and reports them as they
166
166
  stand; what to make of them is yours.
167
- obk groom --bots <path> [--at <HH:MM>] [--on | --off]
168
- Say whether the daily grooming exists, when it runs
169
- and whether it is on, and set it up or change it
170
- when you ask. --at makes one, in Bot Father's Orca
171
- project. It is made off, because it spends tokens
172
- every day: run it by hand once, read what it gives
173
- you, then --on.
167
+ obk groom --bots <path> [--on [--at <HH:MM>] | --at <HH:MM> | --off | --now | --compact]
168
+ Say what the daily grooming is: Bot Father's session
169
+ grooming, and the job scheduled in it on Claude
170
+ Code's own scheduler. Add that session like any
171
+ other, with the model and effort each run is to use.
172
+ --now asks it to run the grooming once. --on --at
173
+ asks it to schedule it daily, and each run renews
174
+ it; it starts off, because it spends tokens every
175
+ day, so run it by hand once and read it first. --at
176
+ moves it, --off unschedules it, and --compact
177
+ compacts its conversation between runs. It fires
178
+ only while its tab is up in Orca.
174
179
  obk usage --bots <path> [--bot <bot>] [--session <name>] [--since <time>] [--until <time>]
175
180
  Say what your sessions have used: the conversations
176
181
  each one had, their calls and tokens, the models and
@@ -281,6 +286,8 @@ async function run(argv) {
281
286
  at: { type: 'string' },
282
287
  on: { type: 'boolean' },
283
288
  off: { type: 'boolean' },
289
+ now: { type: 'boolean' },
290
+ compact: { type: 'boolean' },
284
291
  skill: { type: 'string' },
285
292
  repo: { type: 'string' },
286
293
  ref: { type: 'string' },
@@ -355,7 +362,7 @@ async function run(argv) {
355
362
  /**
356
363
  * The bots folder as the file system knows it. A folder reached through a
357
364
  * symlink is the same fleet: the harnesses file their transcripts under the real
358
- * path, Orca records an automation's workspace by it, and what the kit keeps
365
+ * path, and what the kit keeps
359
366
  * beside the folder (the skill sources, the start prompts, the messages) is
360
367
  * named after it. Asked by one spelling and then another, a fleet would
361
368
  * otherwise have two of each, and a pin set through one is undone through the
@@ -414,7 +421,7 @@ const RECORD = 'session record';
414
421
  * A hook runs inside the user's own session, so this one stays out of the way:
415
422
  * it writes on standard output only what the harness is to read as JSON, and
416
423
  * whatever goes wrong, it goes wrong quietly. A book left stale is a thing the
417
- * health check finds later; a session disturbed is the user's work (ADR 0020).
424
+ * health check finds later; a session disturbed is the user's work (ADR 0022).
418
425
  */
419
426
  async function record(bots, bot) {
420
427
  try {
@@ -658,11 +665,16 @@ const commands = {
658
665
 
659
666
  groom(bots, values) {
660
667
  refuseWhenOrcaIsDown();
661
- if (values.on === true && values.off === true) {
662
- throw new Error('groom takes --on or --off, and got both. Say which one you want.');
668
+ const asks = ['on', 'off', 'now', 'compact'].filter((flag) => values[flag] === true);
669
+ if (asks.length > 1) {
670
+ throw new Error(`groom asks for one thing at a time, and got ${asks.map((flag) => `--${flag}`).join(' and ')}. Say which one you want.`);
663
671
  }
664
- const on = values.on === true ? true : (values.off === true ? false : undefined);
665
- const groom = grooming(bots, { at: values.at, on });
672
+ if (values.at !== undefined && asks.length === 1 && asks[0] !== 'on') {
673
+ throw new Error(`--at is the time grooming runs at, and goes with --on, not with --${asks[0]}.`);
674
+ }
675
+ // --at on its own moves grooming that is on to another time.
676
+ const ask = asks[0] ?? (values.at === undefined ? undefined : 'move');
677
+ const groom = grooming(bots, { at: values.at, ask });
666
678
  return { answer: { bots, groom }, lines: groomLines(groom, bots) };
667
679
  },
668
680
 
@@ -1023,26 +1035,81 @@ function conversationLine(one) {
1023
1035
  }
1024
1036
 
1025
1037
  /**
1026
- * What there is to say about the daily grooming: whether it exists, when it
1027
- * runs, and whether it is on. A grooming that is off is not a fault, so this
1028
- * says what is there and what the next step would be rather than warning.
1038
+ * What there is to say about the daily grooming: the session it runs in, the
1039
+ * jobs scheduled there, and what this run asked of it. Grooming that is off is
1040
+ * not a fault, so this says what is there and what the next step would be;
1041
+ * no job where one was expected, or more than one, is said plainly, because
1042
+ * either means the fleet is groomed a different number of times a day than
1043
+ * anyone decided.
1029
1044
  */
1030
- function groomLines(groom, bots) {
1031
- if (!groom.exists) {
1045
+ function groomLines({ session, jobs, asked }, bots) {
1046
+ const head = 'groom'.padEnd(9);
1047
+ const more = ' '.repeat(11);
1048
+ const said = asked === null ? [] : [ASKED[asked]];
1049
+
1050
+ if (session === null) {
1051
+ return [
1052
+ ...said,
1053
+ `${head} off: Bot Father has no session called grooming, which is where it runs`,
1054
+ `${more}It runs in that session, on Claude Code's own scheduler. Give the session`,
1055
+ `${more}--model and --effort for what each run is to use.`,
1056
+ `Add it: ${addCommand(bots)}`,
1057
+ `Then: ${upCommand(bots)}`,
1058
+ ];
1059
+ }
1060
+ if (session.harness !== 'claude') {
1032
1061
  return [
1033
- `${'groom'.padEnd(9)} there is no daily grooming yet`,
1034
- `Make one: ${shellWord(ownCli())} groom --bots ${shellWord(bots)} --at 04:00`,
1062
+ ...said,
1063
+ `${head} off: Bot Father's grooming session runs on ${session.harness}, and grooming runs`,
1064
+ `${more}on Claude Code's own scheduler, so it has to be a Claude Code session: retire`,
1065
+ `${more}it and add it again with --harness claude.`,
1035
1066
  ];
1036
1067
  }
1037
1068
 
1069
+ const lines = [...said];
1070
+ if (!session.up) {
1071
+ lines.push(`${head} Bot Father's grooming session is not up, so nothing fires`);
1072
+ lines.push(`Bring it up: ${upCommand(bots)}`);
1073
+ }
1074
+ if (asked === null) lines.push(...jobLines(jobs, bots, session.up ? head : more));
1075
+ lines.push(`${more}It fires only while its tab is up in Orca and Claude Code is idle there, up to`);
1076
+ lines.push(`${more}half an hour after its time, and a run it misses is not made up.`);
1077
+ return lines;
1078
+ }
1079
+
1080
+ /** What is scheduled in the grooming session's conversation. */
1081
+ function jobLines(jobs, bots, head) {
1082
+ const more = ' '.repeat(11);
1083
+ if (jobs.length === 0) {
1084
+ return [
1085
+ `${head} off: its conversation has no grooming job`,
1086
+ `${more}It spends tokens every day. Run it by hand first: ${groomCommand(bots)} --now`,
1087
+ `Turn it on, once the user has read a run and said yes: ${groomCommand(bots)} --on --at 04:00`,
1088
+ ];
1089
+ }
1090
+ if (jobs.length === 1) {
1091
+ const [job] = jobs;
1092
+ return [
1093
+ `${head} on, daily at ${job.at ?? job.cron} job ${job.id}`,
1094
+ `${more}Each run renews it; with no run, Claude Code ends it at ${job.expires}.`,
1095
+ `Turn it off: ${groomCommand(bots)} --off`,
1096
+ ];
1097
+ }
1098
+ const times = [...new Set(jobs.map((job) => job.at))];
1038
1099
  return [
1039
- `${'groom'.padEnd(9)} daily at ${groom.at} ${groom.enabled ? 'on' : 'off'}`,
1040
- groom.enabled
1041
- ? `It runs every day at ${groom.at} and spends tokens each time. Turn it off with: ${shellWord(ownCli())} groom --bots ${shellWord(bots)} --off`
1042
- : `It is not running yet. Try it by hand, read what it gives you, then: ${shellWord(ownCli())} groom --bots ${shellWord(bots)} --on`,
1100
+ `${head} ${jobs.length} grooming jobs, so the fleet is groomed ${jobs.length} times a day: ${jobs.map((job) => `${job.id} at ${job.at ?? job.cron}`).join(', ')}`,
1101
+ `Back to one: ${groomCommand(bots)} --on${times.length === 1 && times[0] !== undefined ? '' : ' --at <HH:MM>'}`,
1043
1102
  ];
1044
1103
  }
1045
1104
 
1105
+ /** What each ask typed into the grooming tab, as the line that says so. */
1106
+ const ASKED = {
1107
+ on: `${'asked'.padEnd(9)} the grooming session to schedule the grooming, in a line typed into its tab. It is listed once the session has done it.`,
1108
+ off: `${'asked'.padEnd(9)} the grooming session to delete its grooming jobs, in a line typed into its tab.`,
1109
+ now: `${'asked'.padEnd(9)} the grooming session to run the grooming once, now. Its report goes to Bot Father's management session.`,
1110
+ compact: `${'asked'.padEnd(9)} the grooming session to compact its conversation: /compact was typed into its tab.`,
1111
+ };
1112
+
1046
1113
  /** The settings a session carries, in the order a session is written down. */
1047
1114
  const SHOWN = SESSION_FIELDS.filter((field) => field !== 'name' && field !== 'harness');
1048
1115
 
@@ -49,10 +49,16 @@ export function conversationsIn(harness, home, since) {
49
49
  * is opened, however many conversations the machine has.
50
50
  */
51
51
  export function hasConversation(harness, home, id) {
52
- if (harness === 'claude') return existsSync(path.join(claudeDir(home), `${id}.jsonl`));
52
+ if (harness === 'claude') return existsSync(claudeTranscript(home, id));
53
53
  return rollouts(codexDir()).some((file) => path.basename(file).endsWith(`-${id}.jsonl`));
54
54
  }
55
55
 
56
+ /**
57
+ * Where Claude Code keeps the conversation `id` of this bot home, whether or
58
+ * not it has written it yet.
59
+ */
60
+ export const claudeTranscript = (home, id) => path.join(claudeDir(home), `${id}.jsonl`);
61
+
56
62
  /**
57
63
  * The same conversations, each with the file the harness keeps it in, for a
58
64
  * caller that has to read what is inside one rather than only know it is there,