lobstah 0.5.11 → 0.5.13
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 +2 -1
- package/dist/main.js +4681 -1752
- package/dist/runner.js +1163 -152
- package/docs/configuration.md +29 -7
- package/docs/design.md +1 -1
- package/docs/harness/claude-code.md +7 -3
- package/docs/harness/codex.md +43 -5
- package/docs/man.md +196 -29
- package/docs/pickup.md +10 -0
- package/docs/vocabulary.md +126 -36
- package/package.json +1 -1
package/docs/configuration.md
CHANGED
|
@@ -14,7 +14,7 @@ keys of that section.
|
|
|
14
14
|
|---|---|---|
|
|
15
15
|
| `notifyCommand` | — | Exec'd by the daemon on wake-worthy status transitions with `LOBSTAH_ID`, `LOBSTAH_LANE`, `LOBSTAH_VERB`, `LOBSTAH_NOTE`, `LOBSTAH_AT` in the environment. Fire-and-forget; point it at ntfy, a Slack helper, anything. |
|
|
16
16
|
| `notifyVerbs` | `["needs-decision", "blocked", "done", "failed"]` | Which verbs fire `notifyCommand`. |
|
|
17
|
-
| `attentionKinds` | `["question", "pr:
|
|
17
|
+
| `attentionKinds` | `["question", "pr:ready", "pr:review", "pr:conflict", "pr:checks"]` | Which kinds `man tend`, the glass, and the desktop pet show. `pr:draft` and `landed` are valid opt-in kinds. An explicit list is used unchanged. Unknown kinds are errors. See the [attention contract](vocabulary.md#attention-contract). |
|
|
18
18
|
| `remindSecs` | `900` | An unanswered `needs-decision`/`blocked` re-fires to `man wait`/`man haul` on this interval until answered. `0` = report once only. |
|
|
19
19
|
|
|
20
20
|
## `[repos.<key>]` — workspace definitions
|
|
@@ -26,9 +26,11 @@ The descriptor's `repo` field resolves here; the key is what dispatchers name.
|
|
|
26
26
|
| `path` | yes | The git clone worktrees are allocated from (`~/` expands). |
|
|
27
27
|
| `trunk` | yes (default `main`) | Branch dispatches start from (`origin/<trunk>`). |
|
|
28
28
|
| `origin` | no | Enables clone-on-first-use when `path` doesn't exist. |
|
|
29
|
-
| `setup` | no | Commands run in each fresh worktree, in order (e.g. `["pnpm install"]`). |
|
|
29
|
+
| `setup` | no | Commands run in each fresh worktree, in order (e.g. `["pnpm install"]`). A follow-up that reuses its origin's worktree does not run them again unless a lockfile at the worktree root (`pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, `Cargo.lock`, `go.sum`, and others) or the commands changed since they last ran there. |
|
|
30
|
+
| `scratch` | no | Repo-relative paths (e.g. `["tmp", ".cache"]`) whose untracked files do not count as uncommitted changes when a follow-up decides whether to reuse its origin's worktree. Tracked changes anywhere, and untracked files elsewhere, still do. |
|
|
30
31
|
| `env` | no | Environment merged into every dispatch for this repo. |
|
|
31
32
|
| `pickup` | no (`false`) | Opt this repo into `[pickup.github]` multi-repo mode. Explicit per repo — nothing becomes pickable by being configured. |
|
|
33
|
+
| `pushEarly`, `draftPr`, `checkpointOnStop` | no (inherit `[limits]`) | Override remote preservation for this repo's headless dispatches. |
|
|
32
34
|
|
|
33
35
|
`[repos.<key>.harness]` — per-repo harness defaults: `default` (`claude` \|
|
|
34
36
|
`codex`), `model`, `effort`.
|
|
@@ -53,15 +55,28 @@ Same three keys as the per-repo block. Precedence for every harness setting:
|
|
|
53
55
|
|
|
54
56
|
| Key | Default | Meaning |
|
|
55
57
|
|---|---|---|
|
|
56
|
-
| `maxConcurrent` | `2` |
|
|
57
|
-
| `choreConcurrent` | `1` |
|
|
58
|
-
| `wedgeThresholdSecs` | `600` | No tool activity for this long while alive = wedged → killed and forked with a nudge. |
|
|
58
|
+
| `maxConcurrent` | `2` | Headless work-lane runners the daemon may run at once. Trap-claimed catches use their own sessions and do not spend these slots. |
|
|
59
|
+
| `choreConcurrent` | `1` | Headless chore-lane runner ceiling (rebases and other machine-originated runs). |
|
|
60
|
+
| `wedgeThresholdSecs` | `600` | No tool activity for this long while alive = wedged → killed and forked with a nudge. Also the age past which `status`, `ls`, `man tend`, and the glass show a dispatch's activity line as stale. |
|
|
59
61
|
| `maxRestartAttempts` | `2` | Bounded restart ladder for dead and wedged runners. |
|
|
60
|
-
| `wallClockSecs` | `3600` |
|
|
62
|
+
| `wallClockSecs` | `3600` | Initial active-work window. Progress extends it, up to `maxWallClockSecs`; time paused with `report paused --waiting-on` does not count. |
|
|
63
|
+
| `maxWallClockSecs` | `4 × wallClockSecs` | Hard active-work ceiling across restarts. |
|
|
64
|
+
| `pushEarly` | `true` | For a new chain, push each new committed HEAD to its non-trunk branch on `origin` within 10 seconds. A rejected push is noted and retried only after HEAD moves. A follow-up whose chain already has a PR does not push automatically; its worker pushes to the existing PR's head branch. |
|
|
65
|
+
| `draftPr` | `true` | For a new chain, after first push, adopt an existing PR or open one draft PR when `gh` is available. A follow-up with a chain PR keeps that PR and its watch; it does not open another. |
|
|
66
|
+
| `checkpointOnStop` | `true` | Before a nonterminal stop, checkpoint eligible tracked and untracked files. A new chain then pushes; a follow-up with a chain PR leaves the checkpoint local for its worker to push. Ignored files and secret/build denylist paths are excluded. Set all three switches to `false` for prior runner behavior. |
|
|
67
|
+
|
|
68
|
+
A runner extends its active-work window when a fresh activity event or new HEAD
|
|
69
|
+
shows progress at the boundary. The elapsed budget and current window are
|
|
70
|
+
persisted across restarts; a pause with `--waiting-on` does not spend active
|
|
71
|
+
time. At the hard ceiling, the status verb remains `failed` for compatibility,
|
|
72
|
+
but its note starts `budget:` and tells the man what work was saved and to
|
|
73
|
+
send a continuation.
|
|
61
74
|
| `backgroundWaitSecs` | `1800` | A turn that ends without a report is held open this long while background work the worker started is still running (a push behind a slow pre-push gate); the harness wakes the worker when it settles. Heartbeats keep the wedge detector off the wait. Keep it below `wallClockSecs`, which still ends the run. |
|
|
62
75
|
| `choreRetentionDays` | `7` | Completed chores age out of `chores/done/`. |
|
|
63
76
|
| `attachmentMaxBytes` | `26214400` (25 MiB) | Maximum size of each file supplied with repeatable `dispatch --attach` or `send --attach`. |
|
|
64
77
|
| `retentionDays` | `0` (off) | The daemon culls finished dispatches (done and failed) older than this many days: their `done/` entries, worktrees, state files, and stale PR records and acks. Branches are kept. A dispatch whose PR is still open is kept. Queued and active dispatches are never culled. The pass runs at most once per hour and culls at most 10 dispatches per pass, so it cannot stall the claim loop. Suggested: `14`, the same window as `lobstah cull`. |
|
|
78
|
+
| `releaseOnMerge` | `false` (off) | When a PR watch records `merged` for a PR, the daemon's next cull pass removes the worktree of the dispatch that owns the PR, and of every dispatch in its follow-up chain that ran on that PR (its own worktree or a shared one). It uses the retention cull's removal path, its hourly throttle, and its limit of 10 per pass, and runs even when `retentionDays` is `0`. It releases a worktree only when every dispatch in the chain is finished (done or failed) and none is queued or active, `git status --porcelain` is empty, and after a fetch `git branch -r --contains HEAD` is not empty. Otherwise it keeps the worktree, records why, and `lobstah doctor`'s `disk` row shows `kept: unpushed work`; the next pass checks again. Uncommitted changes and unpushed commits are never deleted. A PR closed without merge releases nothing. A trap's worktree is never released. Branches (local and remote), `done/` entries, state, and evidence are kept; `lobstah catch` prints `worktree: released on merge (<time>)`. One `worktree-released` notice per pass lists what was released. |
|
|
79
|
+
| `reuseWorktree` | `true` | A follow-up (`--follow-up <id>`) runs in the worktree of the newest dispatch in its chain whose worktree still exists, on the branch and HEAD where that dispatch stopped. Trunk is fetched; `setup` runs again only if a lockfile changed. It reuses only when the worktree is clean (untracked files under the repo's `scratch` paths excepted), belongs to the same repo, and no other dispatch runs in it; otherwise it allocates a fresh worktree as usual. A dirty worktree is never cleaned or reset. The first status note says which: `reusing worktree of <origin>` or `fresh worktree (<reason>)`. A lock file in the worktree's git dir (`lobstah.lock`) keeps a second runner out; a lock whose dispatch has finished is stale. A shared worktree is kept by the cull and the free-space guard while any dispatch in the chain is queued or active, and it ages from the newest dispatch that used it. `false` allocates a fresh worktree for every dispatch. Headless dispatches only: a trap works in its own worktree. |
|
|
65
80
|
| `minFreeGB` | `0` (off) | Free space the worktrees volume (`~/.lobstah/worktrees`) must have before the daemon claims work, because each claim creates a worktree (1 to 8 GB). Below the limit, the daemon first removes finished worktrees, oldest first, until the limit is met or none are left (this runs even when `retentionDays` is `0`; open-PR and live worktrees are kept, branches are kept). If space is still short, the work stays in the queue and is not failed: `man tend` and the glass show it as `held: 3.2 GB free, needs 10 GB`. The daemon checks again on every tick. One `disk-held` notice marks the start of a hold and one `disk-cleared` notice marks its end. Suggested: `10`. |
|
|
66
81
|
|
|
67
82
|
## `[soak]` — soaking sessions (`lobstah soak`)
|
|
@@ -69,7 +84,9 @@ Same three keys as the per-repo block. Precedence for every harness setting:
|
|
|
69
84
|
| Key | Default | Meaning |
|
|
70
85
|
|---|---|---|
|
|
71
86
|
| `deferSecs` | `90` | A soaking session whose park heartbeat is this fresh holds unaddressed matching bait — the daemon waits instead of spawning. Addressed bait (`--for session:<id>`) waits regardless, until the registration is gone. |
|
|
72
|
-
| `ttlSecs` | `1800` | Heartbeat age past which a registration is a ghost trap: the sweep removes it and requeues its open catch (or finalizes a cancelled one as failed). A fresh `lobstah report` on the catch counts as liveness too. |
|
|
87
|
+
| `ttlSecs` | `1800` | Heartbeat age past which a registration is a ghost trap: the sweep removes it and requeues its open catch (or finalizes a cancelled one as failed). A fresh `lobstah report` on the catch counts as liveness too, and so does a fresh beat. |
|
|
88
|
+
| `beat` | `true` | The post-tool hook (`lobstah soak beat`) refreshes a soaking session's liveness and writes its catch's activity, at most once per 30 seconds per trap. With `false` the hook does nothing, and a trap's liveness comes from its reports and its park only. |
|
|
89
|
+
| `pausedTtlSecs` | `86400` (24 hours) | A trap whose catch last reported `paused` is kept out of the ghost sweep this long after the report. `report paused --until <iso|duration>` sets the expiry instead. After it, the sweep removes the trap as usual, and the notice says the pause expired. |
|
|
73
90
|
|
|
74
91
|
## `[helm]` — the orchestrator seat (`lobstah man helm`)
|
|
75
92
|
|
|
@@ -90,6 +107,10 @@ Same three keys as the per-repo block. Precedence for every harness setting:
|
|
|
90
107
|
| Key | Default | Meaning |
|
|
91
108
|
|---|---|---|
|
|
92
109
|
| `maxForksPerCycle` | `3` | The most continuation (CI-fix) dispatches one watch cycle of `lobstah pick` may fork. Each watch over the cap is held: its events stay buffered, `man tend` and `lobstah watch` list it as `held`, one `watch-held` notice names the held watches, and it forks nothing until `lobstah watch release <key>` (or `--all`). |
|
|
110
|
+
| `autoRepair` | `true` | On a dispatch-owned PR, fork a repair follow-up for a conflict, failed current check, or requested review changes. `false` leaves check-event delivery and attention as before. |
|
|
111
|
+
| `conflicts` | `true` | Repair conflicts when `autoRepair` is on. Set `false` to show conflict attention without a repair. |
|
|
112
|
+
| `checks` | `true` | Repair failed current checks when `autoRepair` is on. Set `false` to show check attention without a repair. |
|
|
113
|
+
| `maxRepairsPerPr` | `2` | Maximum repair follow-ups for one PR head SHA. When the limit is reached and the issue remains, `pr:conflict`, `pr:checks`, or `pr:review` attention names the limit. |
|
|
93
114
|
|
|
94
115
|
## `[grounds.*]` — helm territories
|
|
95
116
|
|
|
@@ -111,6 +132,7 @@ repos = ["lobstah", "lavish"]
|
|
|
111
132
|
| Key | Default | Meaning |
|
|
112
133
|
|---|---|---|
|
|
113
134
|
| `pollSecs` | `45` | Poll cadence. Outbound only — no webhooks, ever. |
|
|
135
|
+
| `liveComment` | `true` | Keep one editable, marked status comment per dispatch. Routine edits are capped at once per minute; human-needed and terminal transitions still post a fresh notification comment. Falls back to transition comments if editing is unavailable. |
|
|
114
136
|
| `notifyCommand` | — | Pickup's own hook, fired on tracker-report transitions with `LOBSTAH_KEY`, `LOBSTAH_UUID`, `LOBSTAH_VERB`, `LOBSTAH_NOTE`, `LOBSTAH_PR_URL`. |
|
|
115
137
|
|
|
116
138
|
### Token sources (both trackers)
|
package/docs/design.md
CHANGED
|
@@ -702,7 +702,7 @@ with [Preact](https://preactjs.com) and [htm](https://github.com/developit/htm)
|
|
|
702
702
|
|
|
703
703
|
Components are pure functions of the store. A new snapshot re-renders the
|
|
704
704
|
page and Preact's reconciliation changes only the DOM whose data changed:
|
|
705
|
-
rows, cards, and lobs are keyed, so an unchanged row keeps its node, an open
|
|
705
|
+
rows, cards, stack groups, and lobs are keyed, so an unchanged row keeps its node, an open
|
|
706
706
|
modal keeps its nodes across ticks, and scroll positions stay where the
|
|
707
707
|
reader left them — no per-section hashing or manual node preservation. Only
|
|
708
708
|
the active tab's section renders.
|
|
@@ -33,9 +33,13 @@ row, and the session-start brief says when the plugin is behind.
|
|
|
33
33
|
| ----- | ------------ |
|
|
34
34
|
| SessionStart hook (`lobstah man brief`) | Prints the session id and a one-line fleet state. A session that is neither helm nor trap gets the two sign-on commands, with the id filled in. |
|
|
35
35
|
| Stop hook (`lobstah man haul`) | At turn end, blocks with standing attention, or with the command to arm a watcher while work is in flight. Inert unless the session holds the helm or is soaking. |
|
|
36
|
-
|
|
|
36
|
+
| PostToolUse hook (`lobstah soak beat`) | After every tool call in a soaking session: refreshes the trap's liveness and writes its catch's activity (the tool name and its primary target, redacted). At most once per 30 seconds per trap. No network, no git; always exits 0, errors go to `~/.lobstah/logs/beat.log`. Inert when not soaking or with `[soak].beat = false`. |
|
|
37
|
+
| SessionEnd hook (`lobstah stow --quiet`) | Signs a soaking session off when it ends and keeps its worktree. |
|
|
37
38
|
| `man` skill | The orchestrator: the helm, the charter, dispatching, tending, getting woken. |
|
|
38
|
-
| `trap` skill | The worker: soaking
|
|
39
|
+
| `trap` skill | The worker: soaking (in a linked worktree, or in one that soak creates), the `wt:` address, the six report verbs, and `paused --waiting-on` before waiting on something external (for ume, the non-blocking push when the await cannot run as a tracked background task). |
|
|
40
|
+
|
|
41
|
+
`lobstah send <id> "<instruction>"` steers live or queued work and wakes a
|
|
42
|
+
finished dispatch as a follow-up.
|
|
39
43
|
|
|
40
44
|
Slash commands, each a shortcut for a CLI verb:
|
|
41
45
|
|
|
@@ -44,7 +48,7 @@ Slash commands, each a shortcut for a CLI verb:
|
|
|
44
48
|
| `/lobstah:helm [grounds]` | `lobstah man helm` |
|
|
45
49
|
| `/lobstah:relieve` | `lobstah man relieve` |
|
|
46
50
|
| `/lobstah:tend` | `lobstah man tend` |
|
|
47
|
-
| `/lobstah:soak` | `lobstah soak`
|
|
51
|
+
| `/lobstah:soak` | `lobstah soak` |
|
|
48
52
|
| `/lobstah:stow` | `lobstah stow` |
|
|
49
53
|
|
|
50
54
|
Claude Code also loads the `man` and `trap` skills on its own when you ask
|
package/docs/harness/codex.md
CHANGED
|
@@ -34,9 +34,10 @@ says when the plugin is behind.
|
|
|
34
34
|
| ----- | ------------ |
|
|
35
35
|
| SessionStart hook (`lobstah man brief`) | Prints the session id and a one-line fleet state. A session that is neither helm nor trap gets the two sign-on commands, with the id filled in. |
|
|
36
36
|
| Stop hook (`lobstah man haul`) | Parks the session at turn end while work is in flight and wakes it when something needs attention. Inert unless the session holds the helm or is soaking. |
|
|
37
|
-
|
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
37
|
+
| PostToolUse hook (`lobstah soak beat`) | After a tool call in a soaking session: refreshes the trap's liveness and writes its catch's activity. Needs Codex 0.117.0+ (see [below](#post-tool-hook-what-codex-has)). Older Codex ignores the event, and a trap's liveness then comes from its reports and its park only. |
|
|
38
|
+
| SessionEnd hook (`lobstah stow --quiet`) | Signs a soaking session off when it ends and keeps its worktree. |
|
|
39
|
+
| `man` skill | The orchestrator: the helm, the charter, dispatching, tending, getting woken, and sending a follow-up to finished work. |
|
|
40
|
+
| `trap` skill | The worker: soaking (in a linked worktree, or in one that soak creates), the `wt:` address, the six report verbs, and `paused --waiting-on` before external waits. |
|
|
40
41
|
|
|
41
42
|
There are no slash commands: the Codex plugin layout has no commands
|
|
42
43
|
directory. The skills run the same `lobstah` commands as the README
|
|
@@ -60,6 +61,41 @@ command. The desktop task's supplied skill catalog names
|
|
|
60
61
|
`lobstah:lobsterman` and `lobstah:trap`; *the desktop picker and its exact
|
|
61
62
|
inserted mention remain to be verified from the desktop UI*.
|
|
62
63
|
|
|
64
|
+
## Post-tool hook: what Codex has
|
|
65
|
+
|
|
66
|
+
Codex has a `PostToolUse` hook event, with the same `hooks.json` shape as
|
|
67
|
+
Claude Code. Verified in the openai/codex source at commit `e07e58c`:
|
|
68
|
+
|
|
69
|
+
- `codex-rs/hooks/src/lib.rs` lists `PostToolUse` among the hook event names,
|
|
70
|
+
and `codex-rs/config/src/hook_config.rs` maps the `PostToolUse` key.
|
|
71
|
+
- The stdin payload (`PostToolUseCommandInput`, `codex-rs/hooks/src/schema.rs`)
|
|
72
|
+
carries `session_id`, `cwd`, `hook_event_name`, `tool_name`, `tool_input`,
|
|
73
|
+
and `tool_response`: the fields `lobstah soak beat` reads.
|
|
74
|
+
- It arrived in 0.117.0 for shell commands only (openai/codex#15531), gained
|
|
75
|
+
`apply_patch` and MCP tools in 0.124.0 (#18391, #18385), and other local
|
|
76
|
+
function tools in 0.135.0 (#23757).
|
|
77
|
+
- It does not fire for hosted tools such as web search, and it fires only
|
|
78
|
+
when the tool call succeeds (a shell command that exits non-zero still
|
|
79
|
+
counts).
|
|
80
|
+
|
|
81
|
+
Docs: <https://developers.openai.com/codex/hooks>.
|
|
82
|
+
|
|
83
|
+
So on Codex 0.117.0+ a trap beats after its tool calls, like a Claude Code
|
|
84
|
+
trap. On 0.114 to 0.116, Codex has no post-tool hook: a trap's liveness
|
|
85
|
+
comes from its reports and its park only, and a long stretch of work
|
|
86
|
+
without a report can be swept after `[soak].ttlSecs`.
|
|
87
|
+
|
|
88
|
+
## Waiting on something external
|
|
89
|
+
|
|
90
|
+
Before a trap waits on a review, PR, deploy, person, or other external event,
|
|
91
|
+
it reports `paused "<note>" --waiting-on <kind> --link <url>`. Resume with a
|
|
92
|
+
`working` report. A Codex task cannot run an await as a tracked background
|
|
93
|
+
task: use the external tool's non-blocking form and end the turn. See
|
|
94
|
+
[Waiting on](../vocabulary.md#waiting-on).
|
|
95
|
+
|
|
96
|
+
`lobstah send <id> "<instruction>"` steers live or queued work and wakes a
|
|
97
|
+
finished dispatch as a follow-up.
|
|
98
|
+
|
|
63
99
|
## The session id and `--session`
|
|
64
100
|
|
|
65
101
|
Codex exports no session-id variable, so the CLI cannot find the id on its
|
|
@@ -68,11 +104,13 @@ brief prints the task id. Pass it on first sign-on:
|
|
|
68
104
|
|
|
69
105
|
```bash
|
|
70
106
|
lobstah man helm --session <task-id> # the helm
|
|
71
|
-
lobstah soak --session <task-id> # a trap
|
|
107
|
+
lobstah soak --session <task-id> # a trap
|
|
72
108
|
```
|
|
73
109
|
|
|
74
110
|
After sign-on, the Stop hook gets the id from Codex on stdin. A trap's
|
|
75
|
-
identity is its worktree, so `lobstah soak --wait` needs no
|
|
111
|
+
identity is its worktree, so `lobstah soak --wait` in that worktree needs no
|
|
112
|
+
flags. Outside the trap's worktree, `soak --wait`, `report`, and `stow`
|
|
113
|
+
take `--session <task-id>`. Outside
|
|
76
114
|
the hook, `man wait`, `man report`, and `man relieve` still take
|
|
77
115
|
`--session <task-id>`.
|
|
78
116
|
|
package/docs/man.md
CHANGED
|
@@ -17,6 +17,13 @@ you ⇄ liaison (interactive Claude Code / Codex session)
|
|
|
17
17
|
The liaison never watches the workers — the daemon does that with no model in
|
|
18
18
|
the loop. The liaison reads `lobstah status` when you ask, which is the
|
|
19
19
|
token-efficiency point: supervision is a filesystem read, not a conversation.
|
|
20
|
+
Headless runners preserve new-chain committed work on `origin` as HEAD moves
|
|
21
|
+
and open one draft PR when `gh` is available. On a nonterminal stop they
|
|
22
|
+
checkpoint eligible worktree files and push once more. A follow-up whose
|
|
23
|
+
chain already has a PR keeps that PR and its watch. It does not push or open
|
|
24
|
+
a second PR automatically; its worker pushes to the existing PR's head
|
|
25
|
+
branch. The final status note names the saved branch, commit, and PR, so the
|
|
26
|
+
lobstah man can send a continuation without losing work.
|
|
20
27
|
|
|
21
28
|
**The helm is harness-agnostic: drive the fleet from whichever session you
|
|
22
29
|
prefer.** The contract is the CLI, not the harness — a Claude Code session,
|
|
@@ -45,7 +52,11 @@ the background, dispatch it with the `lobstah` CLI instead of doing it inline:
|
|
|
45
52
|
- `lobstah dispatch --repo <key> --brief-text "<full brief>"` — returns an id.
|
|
46
53
|
Write briefs that stand alone; the worker has no other context.
|
|
47
54
|
- `lobstah status [<id>]`, `lobstah ls` — check progress when I ask, not on a loop.
|
|
48
|
-
- `lobstah send <id> "<instruction>"` — steer a
|
|
55
|
+
- `lobstah send <id> "<instruction>"` — steer a live dispatch, add to a queued
|
|
56
|
+
dispatch's inbox, or wake a finished chain as a follow-up. Sending to any
|
|
57
|
+
member of a finished chain follows up its newest member. Use `--no-wake` to
|
|
58
|
+
leave a message in a finished inbox without starting work (nothing reads it).
|
|
59
|
+
A new follow-up accepts `--harness`, `--model`, and `--for wt:<trap>`.
|
|
49
60
|
- `lobstah cancel <id>` — stop one.
|
|
50
61
|
- A dispatch reporting `needs-decision` is waiting on ME — surface its question
|
|
51
62
|
immediately, then `lobstah send` my answer.
|
|
@@ -70,6 +81,21 @@ use by hand. So the crew is inspectable with tools you already have:
|
|
|
70
81
|
the Claude/Codex desktop apps' session lists) show dispatch sessions too —
|
|
71
82
|
they're stored where the harness always stores them.
|
|
72
83
|
|
|
84
|
+
A follow-up (`lobstah dispatch --follow-up <id>`) resumes the origin's
|
|
85
|
+
conversation and, by default, its worktree too: it runs in the checkout of
|
|
86
|
+
the newest dispatch in its chain, on the branch and HEAD where that dispatch
|
|
87
|
+
stopped, without a second dependency install. It allocates a fresh worktree
|
|
88
|
+
instead when that checkout has uncommitted changes, is gone, or another
|
|
89
|
+
dispatch runs in it; its first status note says which (`reusing worktree of
|
|
90
|
+
<origin>` or `fresh worktree (<reason>)`). `lobstah catch` prints the
|
|
91
|
+
`worktree` a dispatch ran in, and `worktreeOf` when it reused one.
|
|
92
|
+
`[limits].reuseWorktree = false` turns reuse off. Normally, use
|
|
93
|
+
`lobstah send <id> "<instruction>"` for a continuation: it delivers to the
|
|
94
|
+
live or queued member of the chain, or creates a follow-up of the newest
|
|
95
|
+
finished member. If that member was last claimed by a trap still signed on,
|
|
96
|
+
the follow-up returns to that trap unless `--for` overrides it. Otherwise it
|
|
97
|
+
is unaddressed for a headless worker.
|
|
98
|
+
|
|
73
99
|
Attach refuses while a dispatch is `working` (two writers, one session);
|
|
74
100
|
follow the logs or `send` instead, or cancel and then attach.
|
|
75
101
|
|
|
@@ -108,6 +134,26 @@ view](pickup.md#merge-view) pickup persists each tick, so PR state is at most
|
|
|
108
134
|
one poll interval stale without tend making a single network call. `--json`
|
|
109
135
|
emits the full report for dashboards and scripts to render.
|
|
110
136
|
|
|
137
|
+
The work table's `activity` column shows what each live dispatch is doing
|
|
138
|
+
now, with its age: `Edit src/a.ts (12s ago)`. It comes from the runner's
|
|
139
|
+
event stream (headless) or the post-tool hook (a trap), never from the
|
|
140
|
+
worker's reports, so it stays current when the worker forgets to report.
|
|
141
|
+
Past `[limits].wedgeThresholdSecs` it reads `stale: … (14m ago)`. A long
|
|
142
|
+
silence is shown, not escalated: it raises no attention and no notice. The
|
|
143
|
+
worker's verb and note stay the primary line. `lobstah status <id>` prints
|
|
144
|
+
the same line as `activity:`, and `lobstah ls` has an `activity` column. See
|
|
145
|
+
[Activity](vocabulary.md#activity).
|
|
146
|
+
|
|
147
|
+
A worker that waits on something outside lobstah (a ume review, a PR
|
|
148
|
+
review, a deploy) reports `paused "<note>" --waiting-on review --link <url>`
|
|
149
|
+
before it waits. Tend, `status`, `ls`, and the glass then show
|
|
150
|
+
`paused: waiting on review` with the link and the time waited. It is a
|
|
151
|
+
state, not a question: nothing to answer, no attention, no pet. A paused
|
|
152
|
+
headless worker is never counted as wedged and its wall clock stops, but it
|
|
153
|
+
still holds a `maxConcurrent` slot while its process is alive. A paused
|
|
154
|
+
trap is kept out of the ghost sweep until `--until` or
|
|
155
|
+
`[soak].pausedTtlSecs` (24 hours). See [Waiting on](vocabulary.md#waiting-on).
|
|
156
|
+
|
|
111
157
|
### PR state after done
|
|
112
158
|
|
|
113
159
|
A dispatch reports `done` when its PR opens; `report done --pr <url>`
|
|
@@ -143,18 +189,27 @@ watches that already exist. `prs sync` refreshes existing PR watches only.
|
|
|
143
189
|
|
|
144
190
|
The first check of a new watch is a baseline:
|
|
145
191
|
|
|
146
|
-
- An open PR:
|
|
147
|
-
|
|
148
|
-
|
|
192
|
+
- An open PR: the first observation records its checks and merge state, but
|
|
193
|
+
starts no repair. Later observations may start a bounded repair on a PR
|
|
194
|
+
lobstah owns.
|
|
149
195
|
- A merged or closed PR: the check records `MERGED` or `CLOSED` in the PR
|
|
150
196
|
record and retires the watch. It forks nothing and raises no attention. A
|
|
151
197
|
`pr-merged` / `pr-closed` notice is posted once only when the PR ended in
|
|
152
198
|
the last 24 hours.
|
|
153
199
|
|
|
154
200
|
One watch cycle forks at most `[watch].maxForksPerCycle` continuations
|
|
155
|
-
(default 3).
|
|
156
|
-
`man tend` and `lobstah watch`.
|
|
157
|
-
|
|
201
|
+
(default 3). Generic watches over the cap are held and listed as `held` in
|
|
202
|
+
`man tend` and `lobstah watch`. PR repairs wait for the next cycle.
|
|
203
|
+
`lobstah watch release <key>` (or `--all`) releases held watches.
|
|
204
|
+
|
|
205
|
+
For a dispatch-owned PR, the watch repairs conflicts, failed current checks,
|
|
206
|
+
and requested review changes. It follows up the newest dispatch in the PR's
|
|
207
|
+
chain. A conflict brief names the PR's base branch, including a stacked
|
|
208
|
+
base. A check brief names each failed check and its details URL. The watch
|
|
209
|
+
records each attempt and stops at `[watch].maxRepairsPerPr` per head SHA.
|
|
210
|
+
It does not repair a PR with a person's newer commits or uncertain commit
|
|
211
|
+
ownership, a terminal PR, or a PR whose chain already has queued or active
|
|
212
|
+
work. `[watch].autoRepair`, `conflicts`, and `checks` control this behavior.
|
|
158
213
|
|
|
159
214
|
Watching a PR nobody dispatched — `lobstah watch add pr:<owner>/<repo>#<n>`
|
|
160
215
|
with no `--for` — is how a helm follows a human's PR, or one whose
|
|
@@ -164,15 +219,25 @@ attention kinds exactly like a dispatched PR (its dispatch chain column is
|
|
|
164
219
|
empty). It stays quiet while it's fine: only a failing check or a changes
|
|
165
220
|
request surfaces as a watch event; a merge or close arrives as a notice.
|
|
166
221
|
|
|
167
|
-
|
|
168
|
-
`
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
222
|
+
**PR order.** Every PR list uses one order: the glass PRs tab, the On deck
|
|
223
|
+
PR stacks, `lobstah prs`, and the `stack #…` lines and the `work` table of
|
|
224
|
+
`man tend`. Open stacks come first, then finished ones. Within each group, the
|
|
225
|
+
stack whose newest PR was first seen last is on top. Within a stack, PRs are
|
|
226
|
+
in stack position. A PR sorts by its record's `firstSeenAt`, then by number.
|
|
227
|
+
A new observation does not change the order. The order changes when a PR
|
|
228
|
+
opens, merges, closes, or changes its base so that it joins or leaves a
|
|
229
|
+
stack. When two PRs share a head branch, the parent is the one first seen
|
|
230
|
+
last. The attention list is in standing order: the time each condition
|
|
231
|
+
started. `observedAt` is shown as the time of the last check.
|
|
232
|
+
|
|
233
|
+
An observed PR joins tend's attention list by kind. `pr:ready` needs a
|
|
234
|
+
mergeable, non-draft PR with no failed, pending, or unknown current checks.
|
|
235
|
+
`pr:conflict` and `pr:checks` appear for an owned PR only when repair is off,
|
|
236
|
+
blocked, or exhausted; their notes say why. Requested review changes on an
|
|
237
|
+
owned PR follow the same repair rule. Unresolved review questions remain
|
|
238
|
+
`pr:review` attention. Drafts are not attention by default; users can add
|
|
239
|
+
`pr:draft` to `attentionKinds`. The glass and desktop pet use the same list.
|
|
240
|
+
See the [attention contract](vocabulary.md#attention-contract). A PR is
|
|
176
241
|
something to look at, not a stall: it never flips the verdict to
|
|
177
242
|
`needs-attention` and stays out of the digest.
|
|
178
243
|
|
|
@@ -302,6 +367,23 @@ must never hide it from the orchestrator that has to answer it. The glass,
|
|
|
302
367
|
which has no write endpoint, hides a clicked lob per browser in localStorage
|
|
303
368
|
instead.
|
|
304
369
|
|
|
370
|
+
**The pet's read.** Every six seconds the pet runs `lobstah attention --json`.
|
|
371
|
+
It prints `{ "attention": [...] }`: the same items, with the same fields, that
|
|
372
|
+
`man tend --json` puts under `attention`, and nothing else. If that command
|
|
373
|
+
fails (an older CLI exits 2 on the unknown flag), the pet runs
|
|
374
|
+
`man tend --json` instead. The pet reads the child's output while the child
|
|
375
|
+
runs, so a report of any size works. Each read may take 10 seconds; then the
|
|
376
|
+
pet stops the child and keeps its current windows. After three failed reads in
|
|
377
|
+
a row it writes one line with the reason to `~/.lobstah/logs/pet.log`, and one
|
|
378
|
+
more line when reads work again. After every read it writes
|
|
379
|
+
`~/.lobstah/pet/state.json`. `lobstah doctor` reads that file for its `pet`
|
|
380
|
+
row: installed or not, running or not, and whether the last read worked:
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
pet ok installed; running (pid 812); last read worked 4s ago (`lobstah attention --json`, 3 walking)
|
|
384
|
+
pet warn installed; running (pid 812); last read failed 2s ago, 3 in a row: `lobstah attention --json` timed out; `lobstah man tend --json` timed out; last worked 5m ago
|
|
385
|
+
```
|
|
386
|
+
|
|
305
387
|
**Wrapper loop.** An outer loop blocking on `wait` can spawn one fresh
|
|
306
388
|
headless turn per event:
|
|
307
389
|
|
|
@@ -393,23 +475,77 @@ visible terminal, and whatever authenticated tooling a headless spawn can't
|
|
|
393
475
|
get.
|
|
394
476
|
|
|
395
477
|
```bash
|
|
396
|
-
lobstah soak #
|
|
397
|
-
#
|
|
398
|
-
# worktree
|
|
478
|
+
lobstah soak # in a linked worktree: sign it on; in a
|
|
479
|
+
# repo's primary checkout: create a linked
|
|
480
|
+
# worktree and sign that on
|
|
399
481
|
# (the id: --session, else hook stdin, else
|
|
400
482
|
# $CLAUDE_CODE_SESSION_ID)
|
|
483
|
+
lobstah soak --repo <key> # outside any configured repo: create a
|
|
484
|
+
# worktree for <key> and sign it on
|
|
401
485
|
lobstah soak --wait # hookless sessions: listen in the foreground
|
|
402
486
|
# (re-runs need no flags — identity is the
|
|
403
|
-
# worktree); exit 3 =
|
|
487
|
+
# worktree, else the session id); exit 3 =
|
|
488
|
+
# quiet, run it again
|
|
404
489
|
lobstah stow # sign off; an open catch requeues, unread
|
|
405
|
-
# messages bounce back to the helm
|
|
490
|
+
# messages bounce back to the helm; removes
|
|
491
|
+
# the worktree when soak created it
|
|
492
|
+
lobstah stow --keep # sign off and keep the worktree
|
|
493
|
+
lobstah soak --name amber-gull # choose or change this trap's two-word name
|
|
406
494
|
```
|
|
407
495
|
|
|
408
|
-
**
|
|
409
|
-
|
|
496
|
+
**Soak can create the worktree.** From a repo's primary checkout, or with
|
|
497
|
+
`--repo <key>` from outside any configured repo, soak creates a linked
|
|
498
|
+
worktree the same way a dispatch does. It fetches trunk, adds
|
|
499
|
+
`~/.lobstah/worktrees/soak-<trap>` on a new branch `lobstah/soak-<trap>`
|
|
500
|
+
from `origin/<trunk>`, and runs the repo's `setup` commands. Without
|
|
501
|
+
`--repo`, soak run outside a configured repo fails with an error that names
|
|
502
|
+
`--repo`. In a linked worktree, `--repo` must match that worktree's repo.
|
|
503
|
+
Soak prints `worktree: <path>`, `created: true`, and `branch:`. When the
|
|
504
|
+
session is not inside the worktree, it also prints
|
|
505
|
+
`instruction: cd <path> and work in that directory from now on`, and its
|
|
506
|
+
help lines carry `--session <id>`. The session changes into that directory
|
|
507
|
+
before it takes work. If creation fails (setup fails, the
|
|
508
|
+
`[limits].minFreeGB` check fails, or trunk cannot be fetched), soak signs
|
|
509
|
+
nothing on, removes the partial worktree and its branch, and prints the
|
|
510
|
+
cause. The address is `wt:<trap>`. The registration records
|
|
511
|
+
`createdWorktree: true`, and `.lobstah-trap` records `createdBy: "soak"`,
|
|
512
|
+
the session id, the repo, and the branch.
|
|
513
|
+
|
|
514
|
+
Soak is idempotent. A session that already mans a trap re-uses it when it
|
|
515
|
+
runs `soak` or `soak --wait` outside a linked worktree: the trap is resolved
|
|
516
|
+
from the session id, and no second worktree is created. A worktree that soak
|
|
517
|
+
created for the same session and repo is also re-used after a ghost sweep.
|
|
518
|
+
From outside the worktree, `soak --wait`, `report`, and `stow` find the
|
|
519
|
+
trap by session id (`--session <id>`, or `$CLAUDE_CODE_SESSION_ID`), and
|
|
520
|
+
`send session:<id>` addresses it. With `--session`, or from inside the worktree, `report done` records the
|
|
521
|
+
worktree's HEAD commit and branch in evidence.
|
|
522
|
+
|
|
523
|
+
`stow` removes a worktree only when soak created it and nothing in it exists
|
|
524
|
+
elsewhere. It keeps the worktree and prints `worktree: kept` and a `reason:`
|
|
525
|
+
when soak did not create the worktree, or when the worktree has uncommitted
|
|
526
|
+
changes, untracked files that are not ignored, or commits on no remote
|
|
527
|
+
branch. Ignored files do not block removal. Stow never forces a removal.
|
|
528
|
+
Stow runs the removal from the primary checkout, so it works from inside
|
|
529
|
+
the worktree. On removal it prints `worktree: removed`, `path:`, and
|
|
530
|
+
`returnTo: <primary checkout>`, with a help line `cd <primary>`. It
|
|
531
|
+
deletes the branch only when every commit on it is on its upstream (with no
|
|
532
|
+
upstream: on some remote branch);
|
|
533
|
+
otherwise it prints `branchKept: <branch> (<reason>)`. A deleted branch
|
|
534
|
+
prints as `branchDeleted:`. `stow --wt <id>` follows the same rules. The
|
|
535
|
+
SessionEnd hook (`lobstah stow --quiet`) signs off and keeps the worktree.
|
|
536
|
+
|
|
537
|
+
**Identity is the worktree.** Sign-on anchors a short trap id and two-word
|
|
538
|
+
name in `.lobstah-trap` and prints both, such as `amber-gull (wt:c32a245d)`; the address
|
|
410
539
|
survives session restarts — a new session in the same worktree resumes the
|
|
411
540
|
same trap (a *live* foreign session is refused: the session lock). The
|
|
412
|
-
|
|
541
|
+
name is reserved across all traps known to this lobstah home, including
|
|
542
|
+
stowed and swept traps. `--name` sets or changes it; malformed and taken
|
|
543
|
+
names are refused. The bare name, `wt:<name>`, and `wt:<id>` all address the
|
|
544
|
+
same live trap in `dispatch --for`, `send`, and `stow --wt`. Unknown names
|
|
545
|
+
list known names and never turn addressed bait into headless work. The id
|
|
546
|
+
remains the key in dispatch and claim records.
|
|
547
|
+
|
|
548
|
+
The session id (from the plugin's session-start brief) lives inside the
|
|
413
549
|
registration as the liveness principal. The harness (claude or codex) is
|
|
414
550
|
inferred — from `CLAUDE*` / `CODEX*` in the environment, and when both are
|
|
415
551
|
set (one harness launched inside the other) from the session id's format:
|
|
@@ -430,9 +566,17 @@ the daemon spawns headless. Conversational steering goes through
|
|
|
430
566
|
`send wt:<trap> "..."` — a message, not bait: no branch, no catch, sender
|
|
431
567
|
stamped, bounced to the helm when undeliverable.
|
|
432
568
|
|
|
569
|
+
A soaking session proves it is alive three ways: its park heartbeat, its
|
|
570
|
+
reports, and its **beat**. The plugin's post-tool hook runs
|
|
571
|
+
`lobstah soak beat` after tool calls: at most once per 30 seconds it
|
|
572
|
+
refreshes the trap's beat and writes the claimed catch's activity. The hook
|
|
573
|
+
resolves the trap from the working directory, else from the session id. A
|
|
574
|
+
trap that works for an hour without reporting is not swept while it beats.
|
|
575
|
+
`[soak].beat = false` turns the hook off.
|
|
576
|
+
|
|
433
577
|
Liveness has two failure shapes with two remedies: a registration that
|
|
434
|
-
parked before and went quiet past `[soak].ttlSecs` is a **ghost trap** —
|
|
435
|
-
swept, catch requeued, noticed; one that **never parked** is a **defective
|
|
578
|
+
parked before and went quiet (no park, report, or beat) past `[soak].ttlSecs` is a **ghost trap** —
|
|
579
|
+
swept, catch requeued, noticed, its worktree kept; one that **never parked** is a **defective
|
|
436
580
|
enlistment** — noticed with its diagnosis (usually a missing Stop hook →
|
|
437
581
|
`soak --wait`) and left standing so the address keeps protecting its work.
|
|
438
582
|
Nobody is conscripted: only a worktree whose session ran `soak` ever
|
|
@@ -444,7 +588,14 @@ Worktrees are 1 to 8 GB each. `lobstah cull` sweeps what is finished: `done/`
|
|
|
444
588
|
entries older than the window (`--older-than <days>`, default 14), worktrees
|
|
445
589
|
whose dispatch is finished or gone, stale state files, merged or closed PR
|
|
446
590
|
records, and orphaned acks. It never touches queued or active work, and
|
|
447
|
-
`git worktree remove` keeps each dispatch's branch.
|
|
591
|
+
`git worktree remove` keeps each dispatch's branch. A worktree that soak
|
|
592
|
+
created counts as in use while a trap registration anchors it. After that,
|
|
593
|
+
the cull and the daemon's retention and free-space culls treat it like any
|
|
594
|
+
other worktree that no dispatch owns: it ages out after
|
|
595
|
+
`[limits].retentionDays`. A worktree that follow-ups
|
|
596
|
+
reused is one worktree shared by the chain: it stays while any dispatch in
|
|
597
|
+
the chain is queued or active, and it ages from the newest dispatch that
|
|
598
|
+
used it.
|
|
448
599
|
|
|
449
600
|
Without `--apply` it is a dry run: it measures each target and prints the
|
|
450
601
|
sizes. A worktree is measured with one `du -sk`; where `du` is missing
|
|
@@ -476,8 +627,24 @@ The daemon can do this on its own. Two `[limits]` keys turn it on
|
|
|
476
627
|
note column, and one `disk-held` notice reaches the helm. When space
|
|
477
628
|
returns, one `disk-cleared` notice follows and claiming resumes.
|
|
478
629
|
|
|
479
|
-
|
|
480
|
-
|
|
630
|
+
A third key, `releaseOnMerge`, frees a worktree as soon as its PR merges
|
|
631
|
+
instead of waiting out `retentionDays`. When a PR watch records `merged`, the
|
|
632
|
+
next cull pass removes the worktree of the dispatch that owns the PR and of
|
|
633
|
+
every dispatch in its follow-up chain that ran on that PR. It checks first:
|
|
634
|
+
every dispatch in the chain is finished, the worktree is clean, and its HEAD
|
|
635
|
+
is on the remote after a fetch. A worktree that fails a check is kept and
|
|
636
|
+
listed as `kept: unpushed work`. A PR closed without merge releases nothing,
|
|
637
|
+
and a trap's worktree is never released. Branches and the dispatch record
|
|
638
|
+
stay; `lobstah catch` says `worktree: released on merge (<time>)`. One
|
|
639
|
+
`worktree-released` notice per pass lists what went.
|
|
640
|
+
|
|
641
|
+
`lobstah doctor` prints a `disk` row: free space on the worktrees volume, the
|
|
642
|
+
limits, the count and age of the worktrees a cull could remove, and any
|
|
643
|
+
merged PR's worktree that `releaseOnMerge` kept, with the reason:
|
|
644
|
+
|
|
645
|
+
```
|
|
646
|
+
disk warn 412.3 GB free on ~/.lobstah/worktrees; minFreeGB 10; retentionDays 14; 3 cullable worktree(s), oldest 9d; releaseOnMerge on; kept: unpushed work (1 worktree(s) of merged PRs: 6a1f0c2e HEAD is not on the remote)
|
|
647
|
+
```
|
|
481
648
|
|
|
482
649
|
## What the daemon gives your liaison for free
|
|
483
650
|
|
package/docs/pickup.md
CHANGED
|
@@ -54,6 +54,16 @@ fire-and-forget, never blocking the loop. Point it at whatever the host
|
|
|
54
54
|
already has (a Slack helper, `openclaw message send`); lobstah stays free of
|
|
55
55
|
messaging vendors.
|
|
56
56
|
|
|
57
|
+
**Live status.** With `[pickup].liveComment = true` (the default), pickup
|
|
58
|
+
creates one marked, editable comment per dispatch on Linear or GitHub. It
|
|
59
|
+
shows the current verb, waiting reason/link, latest activity and staleness,
|
|
60
|
+
elapsed time, attempt, branch, last commit, commits ahead of trunk, and draft
|
|
61
|
+
PR when known. Pickup edits it at most once per minute unless the verb changes,
|
|
62
|
+
and does not rewrite it when the underlying status is unchanged. A
|
|
63
|
+
`needs-decision`, `blocked`, `failed`, or `done` transition also gets a new
|
|
64
|
+
comment so humans are notified. If the tracker cannot edit the live comment,
|
|
65
|
+
pickup continues with the older transition-comment behavior.
|
|
66
|
+
|
|
57
67
|
**No LLM in the loop.** Issue-to-descriptor translation is mechanical. Judgment
|
|
58
68
|
about an ambiguous issue belongs to the dispatched agent, which reports
|
|
59
69
|
`needs-decision` — not to pickup. Fleet setups that wake an LLM to poll a
|