lobstah 0.2.1 → 0.3.1

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/dist/runner.js CHANGED
@@ -767,6 +767,10 @@ function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
767
767
  }
768
768
 
769
769
  // packages/core/dist/config.js
770
+ var DEFAULT_SOAK = {
771
+ deferSecs: 90,
772
+ ttlSecs: 1800
773
+ };
770
774
  var DEFAULT_LIMITS = {
771
775
  maxConcurrent: 2,
772
776
  choreConcurrent: 1,
@@ -801,6 +805,7 @@ function loadConfig() {
801
805
  repos,
802
806
  harness: raw.harness ?? {},
803
807
  limits: { ...DEFAULT_LIMITS, ...raw.limits ?? {} },
808
+ soak: { ...DEFAULT_SOAK, ...raw.soak ?? {} },
804
809
  notifyCommand: raw.notifyCommand ? String(raw.notifyCommand) : void 0,
805
810
  notifyVerbs: Array.isArray(raw.notifyVerbs) ? raw.notifyVerbs.map(String) : void 0,
806
811
  remindSecs: raw.remindSecs !== void 0 ? Number(raw.remindSecs) : void 0
Binary file
Binary file
@@ -53,6 +53,13 @@ Same three keys as the per-repo block. Precedence for every harness setting:
53
53
  | `wallClockSecs` | `3600` | Hard per-dispatch ceiling, enforced by the runner. |
54
54
  | `choreRetentionDays` | `7` | Completed chores age out of `chores/done/`. |
55
55
 
56
+ ## `[soak]` — soaking sessions (`lobstah soak`)
57
+
58
+ | Key | Default | Meaning |
59
+ |---|---|---|
60
+ | `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. |
61
+ | `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. |
62
+
56
63
  ## `[pickup]` — tracker loops (`lobstah pick`)
57
64
 
58
65
  | Key | Default | Meaning |
@@ -141,9 +141,10 @@ trapline; every orchestrator-facing command lives under `lobstah man`).
141
141
  Install it from the project you'll run the lobsterman in:
142
142
 
143
143
  ```bash
144
- # Easiest: the Claude Code plugin ships the hook + the lobsterman skill +
145
- # /lobstah, no settings edits — /plugin marketplace add aequitas-labs/lobstah,
146
- # /plugin install lobstah@lobstah. Or wire the hook by hand:
144
+ # Easiest: the plugin ships the hooks + the lobsterman skill, no settings
145
+ # edits — /plugin marketplace add aequitas-labs/lobstah, then
146
+ # /plugin install lobstah@lobstah (Claude Code and Codex v0.114+; Codex asks
147
+ # for a one-time hook trust review). Or wire the Claude hook by hand:
147
148
  lobstah man init # merges the Stop hook into .claude/settings.local.json
148
149
  lobstah man init --shared # …or the committed .claude/settings.json
149
150
  lobstah man init --global # …or once into ~/.claude/settings.json — any
@@ -173,11 +174,11 @@ the haul context as the work order for that turn.
173
174
  The trade-offs, honestly. While parked, the turn never ends, so the terminal
174
175
  shows a running hook. Each wake appends a turn to the context, and long
175
176
  shifts eventually compact. Without the gate, the hook parks every session in
176
- the project. And parking is Claude-specific: it needs a turn-end hook that
177
- can block and inject a continuation. Claude Code's Stop hook can. Codex's
178
- fire-and-forget notify hook cannot. The workers can still be any harness —
179
- only the liaison must be Claude Code. Tier 2 is the right default. Tier 3 is
180
- for a dedicated, long-lived liaison session.
177
+ the project. And parking needs a turn-end hook that
178
+ can block and inject a continuation. Claude Code's Stop hook can, and so can
179
+ Codex's since its hooks system landed (v0.114+; older Codex only has the
180
+ fire-and-forget notify hook, which cannot). Tier 2 is the right default.
181
+ Tier 3 is for a dedicated, long-lived liaison session.
181
182
 
182
183
  **Delivery guarantee.** Attention wakes are at-least-once with backoff. An
183
184
  unanswered question is reported immediately. While it still stands, it
@@ -201,6 +202,35 @@ Zero tokens between events and works anywhere a shell does; the cost is that
201
202
  each event gets a fresh context rather than a continuing liaison
202
203
  conversation.
203
204
 
205
+ ## Soaking: a live session volunteers as a worker
206
+
207
+ Workers are usually traps lobstah sets itself — fresh headless sessions in
208
+ fresh worktrees. A **soaking** session is the inverse: an interactive thread
209
+ already in the water volunteers to take bait, keeping its warm context, its
210
+ visible terminal, and whatever authenticated tooling a headless spawn can't
211
+ get.
212
+
213
+ ```bash
214
+ lobstah soak --session <id> # from a worktree — the primary checkout is
215
+ # never claimable, so sign on from a linked
216
+ # worktree (git worktree add ../side -b side)
217
+ lobstah stow --session <id> # sign off; an open catch requeues
218
+ ```
219
+
220
+ The session id comes from the plugin's session-start brief (`lobstah man
221
+ brief` announces it into the conversation). Once soaking, the same Stop hook
222
+ that parks a lobsterman parks the worker: at turn end it waits for bait,
223
+ claims it, and wakes with the brief. While it works a catch, the park wakes
224
+ it for `lobstah send` messages and cancels instead.
225
+
226
+ Routing follows ownership: `dispatch --for session:<id>` targets one trap;
227
+ unaddressed bait for a matching repo prefers a parked trap for
228
+ `[soak].deferSecs` before the daemon spawns headless; a watch continuation
229
+ for a chain a soaking session claimed is addressed back to that session. A
230
+ registration whose heartbeat lapses past `[soak].ttlSecs` is a **ghost
231
+ trap** — swept, its catch requeued. Nobody is conscripted: only a session
232
+ that ran `soak` ever receives work.
233
+
204
234
  ## What the daemon gives your liaison for free
205
235
 
206
236
  - Parallel work that can't collide — worktree per dispatch.
package/docs/pickup.md CHANGED
@@ -92,12 +92,30 @@ The three loops are tracker-agnostic and drive whichever sources are configured.
92
92
  | Rule | Trigger | Descriptor |
93
93
  |---|---|---|
94
94
  | Issue pickup | Assigned to the configured identity, in the configured start state | Implementation brief from the issue |
95
- | Review pickup | Open PR authored by the configured identity with `CHANGES_REQUESTED`, or a human review newer than HEAD | Address-review brief from the feedback |
96
-
97
- A review dispatch sets `followUp` to the implementation dispatch's UUID,
98
- forking that session so the feedback lands on the context that made the
99
- choices. A rebase chore starts cold on purpose — the conflict is about commits
100
- the original session never saw.
95
+ | Feedback pickup | Human feedback on an open PR that maps back to a dispatch | Address-feedback brief; the agent reads the live thread |
96
+
97
+ A dispatch reports `done` when its PR opens, so the reviewer's side of the
98
+ conversation has to re-enter the queue as its own work. Feedback pickup
99
+ covers all of it: a `CHANGES_REQUESTED` review on any sha (a review of a
100
+ slightly stale head still asks for changes), a comment review with a body,
101
+ an inline review-thread comment, or a conversation comment — always from
102
+ someone other than the configured identity, never a lobstah marker comment.
103
+ `APPROVED` reviews are the merge loop's signal, not feedback.
104
+
105
+ The PR maps back to a dispatch two ways: a `lobstah/<uuid>` branch names it
106
+ directly, and any other branch — a soaked session's PR — resolves through
107
+ the PR URL its worker reported as evidence (`report done --pr <url>`). A PR
108
+ that maps to neither is not lobstah's to answer.
109
+
110
+ Each feedback **round** is keyed by the newest feedback event, so a new
111
+ comment after a round completes opens the next round, while the same standing
112
+ feedback never double-dispatches. Rounds on one PR serialize — a round that
113
+ arrives while the previous one runs buffers until it finishes — and each
114
+ round sets `followUp` to the latest session in the chain (the previous
115
+ round, else the implementation dispatch), so feedback lands on the context
116
+ that made the choices; a fully culled chain starts cold and reads the thread
117
+ like anyone else. A rebase chore starts cold on purpose — the conflict is
118
+ about commits the original session never saw.
101
119
 
102
120
  ### Claiming
103
121
 
@@ -339,7 +357,7 @@ Pickup's three loops and the daemon absorb that whole layer:
339
357
  | Fleet-script job | Fate |
340
358
  |---|---|
341
359
  | Poll tracker, dispatch assigned issues | Dispatch loop, issue rule |
342
- | Watch PRs for review feedback | Dispatch loop, review rule |
360
+ | Watch PRs for review feedback | Dispatch loop, feedback rule |
343
361
  | Per-dispatch outcome checks and cron re-arms | Daemon supervision + `report` |
344
362
  | Merge approved PRs | Merge loop |
345
363
  | Detect in-progress issues nothing backs | Dead/wedged half → daemon; tracker-drift half → reconciliation loop |
@@ -137,3 +137,39 @@ Delivery is level-triggered and at-least-once, like dispatch attention:
137
137
  events stand until the owner consumes them. One continuation dispatch in
138
138
  flight per watch; later events buffer and fork from the latest session in
139
139
  the chain.
140
+
141
+ ## Soaking contract
142
+
143
+ A **soaking trap** is a live interactive session that volunteered as a worker
144
+ through `lobstah soak` — the validated write path; nothing else touches
145
+ `soaking/`. Sub-agent workers are the traps lobstah sets itself; a soaking
146
+ session is a trap already in the water, and dispatch drops bait into it
147
+ before building a new one. **Owner:** `packages/core/src/soak.ts`.
148
+ **Enforcement:** sign-on is refused from a repo's primary checkout (never
149
+ claimable) and when another session already soaks the same worktree.
150
+
151
+ | Word | Meaning |
152
+ | ---- | ------- |
153
+ | `soak` | Sign a session on: it parks at turn end (Stop hook) and takes matching bait from the work queue. `--one` stows after the first catch. |
154
+ | `stow` | Sign a session off; an open catch goes back to the queue (a cancelled one finalizes as failed). |
155
+ | bait address | `--for session:<id>` on a dispatch targets one soaking session. Addressed bait waits for its trap until the registration is gone; unaddressed bait defers to a parked matching trap for `[soak].deferSecs`, then the daemon spawns headless. |
156
+ | catch | The active dispatch a soaking session claimed (`claim.json` in the active dir). One catch per trap; one active item per worktree. The daemon never spawns or restarts it — the session's reports are its liveness. |
157
+ | ghost trap | A registration whose heartbeat lapsed past `[soak].ttlSecs` — a lost trap that keeps fishing. The sweep hauls it out and requeues its catch. A fresh report on the catch keeps a mid-turn session out of the sweep. |
158
+
159
+ Delivery routes by ownership, same as watches: a continuation for a chain
160
+ claimed by a live soaking session is addressed back to that session; once it
161
+ ghosts, the same bait forks headless. Sessions are never conscripted — a
162
+ thread works bait only after opting in.
163
+
164
+ ## Exit codes
165
+
166
+ The CLI's exit-code contract, aligned with axi.md P6. **Owner:**
167
+ `apps/cli/src/main.ts` (`UsageError`) and `apps/cli/src/usage.ts` (the
168
+ registry that decides what parses).
169
+
170
+ | Code | Meaning |
171
+ | ---- | ------- |
172
+ | `0` | Success — including definitive empty results. |
173
+ | `1` | Error: the command was well-formed but could not do its job. |
174
+ | `2` | Usage mistake: unknown command, flag, or subverb. The error names the offender and prints the command's usage card. Flags in a free-text tail (a `send` message, a `report` note) are never validated — prose may contain anything. |
175
+ | `3` | `man wait --timeout` elapsed with nothing to report. Its own code so `while lobstah man wait` loops still terminate on timeout while `2` stays unambiguous. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lobstah",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Harness-agnostic, token-efficient supervision framework for coding agents",
5
5
  "license": "MIT",
6
6
  "author": "aequitas labs LLC",