@sentropic/h2a 0.85.18 → 0.85.20

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 (41) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/dist/bin.js +6 -6
  4. package/dist/bin.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +26 -1
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +4 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +4 -2
  11. package/dist/index.js.map +1 -1
  12. package/dist/runtime/local-files/index.d.ts +1 -1
  13. package/dist/runtime/local-files/index.d.ts.map +1 -1
  14. package/dist/runtime/local-files/index.js +1 -1
  15. package/dist/runtime/local-files/index.js.map +1 -1
  16. package/dist/runtime/local-files/lease.d.ts +21 -0
  17. package/dist/runtime/local-files/lease.d.ts.map +1 -1
  18. package/dist/runtime/local-files/lease.js +25 -0
  19. package/dist/runtime/local-files/lease.js.map +1 -1
  20. package/dist/runtime/loop/engine/execute.d.ts +3 -1
  21. package/dist/runtime/loop/engine/execute.d.ts.map +1 -1
  22. package/dist/runtime/loop/engine/execute.js +8 -1
  23. package/dist/runtime/loop/engine/execute.js.map +1 -1
  24. package/dist/runtime/loop/engine/tick.d.ts +1 -0
  25. package/dist/runtime/loop/engine/tick.d.ts.map +1 -1
  26. package/dist/runtime/loop/engine/tick.js +11 -7
  27. package/dist/runtime/loop/engine/tick.js.map +1 -1
  28. package/dist/runtime/loop/executor-lease.d.ts +44 -0
  29. package/dist/runtime/loop/executor-lease.d.ts.map +1 -0
  30. package/dist/runtime/loop/executor-lease.js +63 -0
  31. package/dist/runtime/loop/executor-lease.js.map +1 -0
  32. package/dist/runtime/loop/index.d.ts +58 -0
  33. package/dist/runtime/loop/index.d.ts.map +1 -1
  34. package/dist/runtime/loop/index.js +91 -1
  35. package/dist/runtime/loop/index.js.map +1 -1
  36. package/dist/runtime/loop/supervisor.d.ts +155 -0
  37. package/dist/runtime/loop/supervisor.d.ts.map +1 -0
  38. package/dist/runtime/loop/supervisor.js +263 -0
  39. package/dist/runtime/loop/supervisor.js.map +1 -0
  40. package/package.json +2 -2
  41. package/skills/harness/using-harness/SKILL.md +18 -0
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Durable objective-loop supervisor (L1 Lot 1b).
3
+ *
4
+ * THIS is the piece that makes an h2a objective loop auto-relaunch on its own,
5
+ * the way `/loop` does on Claude — but server-side and host-agnostic. Each beat
6
+ * it takes the loops that have OPTED IN (`policy.autoTick`, Lot 1a) and, for
7
+ * each one it can lease as the SINGLE writer (`acquireLoopExecutorLease`, Lot 0),
8
+ * runs exactly one `tick + execute` of the existing engine under that lease,
9
+ * then releases it. It reuses the engine — it does not re-implement ticking.
10
+ *
11
+ * Safety, per the double-opus B′ consensus (tmp/L1-decision-reconciled.md):
12
+ * - OPT-IN ONLY: `listAutoTickLoops` already filters to opted-in, LIVE loops;
13
+ * a global kill-switch (`H2A_LOOP_AUTOTICK_OFF`) freezes every beat.
14
+ * - SINGLE-WRITER (bounded): a loop is ticked only while its per-loop lease is
15
+ * held, AND each tick is bounded by a lease-safety timeout that is a fraction
16
+ * of the lease TTL — so a cooperative tick always completes and releases
17
+ * BEFORE its lease could become stealable, and the dozen `mcp-serve`
18
+ * processes on a host cannot double-tick it. A pathological tick that overruns
19
+ * the timeout is ABORTED (fail-closed): the engine stops firing new
20
+ * non-idempotent actions and the beat moves on. Residual limits (a single
21
+ * in-flight action cannot be un-fired, and the underlying lease-release has a
22
+ * known TOCTOU on overrun) are tracked follow-ups — full effect-fencing
23
+ * (thread the fencing token into the sink + re-check before each action, and
24
+ * make adapters cancellable) is deferred, safe under the opt-in-off default.
25
+ * - FAIL-CLOSED ISOLATION: one loop throwing or timing out never aborts the
26
+ * beat or the supervisor; that loop simply goes un-ticked this beat and its
27
+ * heartbeat goes stale, which `loopAttendance` surfaces as `unattended`.
28
+ * - HONEST ATTENDANCE: each successful tick stamps an executor heartbeat;
29
+ * `loopAttendance` is a pure read-side predicate ANY reader (status/doctor)
30
+ * can evaluate — an opted-in loop with no fresh heartbeat reads `unattended`
31
+ * (fail-closed: missing / stale / implausibly-future ⇒ unattended), so a down
32
+ * supervisor is visible even under clock skew.
33
+ * - DRAINABLE: `signal` is honored BETWEEN loops within a beat and threaded
34
+ * into the tick, so a SIGTERM stops launching new work promptly instead of
35
+ * draining every remaining eligible loop first.
36
+ *
37
+ * Golden rule: this module must NOT statically import `@sentropic/h2a-runtime`.
38
+ * It imports only the loop store, the executor lease, and the engine tick (which
39
+ * is itself adapters-gated). Imported constants are referenced at CALL time, not
40
+ * aliased at module-eval, to avoid any import-cycle temporal-dead-zone.
41
+ */
42
+ import { readFileSync, writeFileSync } from "node:fs";
43
+ import { hostname } from "node:os";
44
+ import { join } from "node:path";
45
+ import { localStorePaths, safePathSegment } from "../local-files/paths.js";
46
+ import { autoTickGloballyDisabled, isLoopAutoTickEligible, listAutoTickLoops } from "./index.js";
47
+ import { acquireLoopExecutorLease } from "./executor-lease.js";
48
+ import { runTick } from "./engine/tick.js";
49
+ /** Default supervisor beat interval. Each eligible loop is ticked every beat. */
50
+ export const DEFAULT_SUPERVISOR_INTERVAL_MS = 30000;
51
+ /**
52
+ * Executor-lease TTL the supervisor acquires with — deliberately LARGER than the
53
+ * per-loop-lease default (30s) so a normal tick (which may boot an agent) fits
54
+ * comfortably inside it. The lease-safety timeout below is a fraction of this, so
55
+ * a cooperative tick always releases before the lease could be stolen.
56
+ */
57
+ export const DEFAULT_SUPERVISOR_LEASE_MS = 120000;
58
+ /**
59
+ * Fraction of the executor lease after which an in-flight tick is aborted
60
+ * (fail-closed). 0.5 leaves a full half-TTL of margin between "we gave up on this
61
+ * tick and released" and "the lease becomes stealable", so a cooperative tick can
62
+ * never overlap a successor executor.
63
+ */
64
+ export const TICK_TIMEOUT_FRACTION = 0.5;
65
+ /**
66
+ * How many of a loop's own `tickMs` may elapse with no executor heartbeat before
67
+ * an opted-in loop is judged `unattended`. K=3 tolerates a couple of missed
68
+ * beats (a busy host, a lease held by a slow peer) before raising the flag.
69
+ */
70
+ export const DEFAULT_UNATTENDED_TICKS = 3;
71
+ /** Fallback per-loop cadence when a loop's policy carries no `tickMs`. */
72
+ const FALLBACK_TICK_MS = 60000;
73
+ /** Default heartbeat/lease holder label — host + pid, so racing writers differ. */
74
+ function defaultHolder() {
75
+ return `${hostname()}:${process.pid}`;
76
+ }
77
+ /** Absolute path of a loop's executor heartbeat file. */
78
+ export function loopExecutorHeartbeatPath(root, loopId) {
79
+ return join(localStorePaths(root).root, "loops", safePathSegment(loopId), "executor.heartbeat");
80
+ }
81
+ /**
82
+ * Record that an executor just ticked `loopId`. Written after a successful tick,
83
+ * inside the loop's own dir (created by the lease acquisition). Best-effort: a
84
+ * write failure must not crash the beat, so callers wrap it — but it throws
85
+ * nothing on the happy path.
86
+ */
87
+ export function stampExecutorHeartbeat(root, loopId, entry) {
88
+ const hb = {
89
+ at: new Date(entry.at ?? Date.now()).toISOString(),
90
+ holder: entry.holder,
91
+ fencingToken: entry.fencingToken
92
+ };
93
+ writeFileSync(loopExecutorHeartbeatPath(root, loopId), JSON.stringify(hb));
94
+ }
95
+ /** Read a loop's executor heartbeat, or `null` if absent/unparseable. */
96
+ export function readExecutorHeartbeat(root, loopId) {
97
+ let raw;
98
+ try {
99
+ raw = readFileSync(loopExecutorHeartbeatPath(root, loopId), "utf8");
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ try {
105
+ const p = JSON.parse(raw);
106
+ if (typeof p.at === "string" && typeof p.holder === "string" && typeof p.fencingToken === "number") {
107
+ return { at: p.at, holder: p.holder, fencingToken: p.fencingToken };
108
+ }
109
+ }
110
+ catch {
111
+ /* malformed — treat as absent */
112
+ }
113
+ return null;
114
+ }
115
+ /**
116
+ * Read-side, fail-closed attendance of an opted-in loop. Any reader can call
117
+ * this without a supervisor running:
118
+ * - `not-applicable` — the loop is not auto-tick eligible (not opted-in,
119
+ * terminal, or the kill-switch is on): nothing should tick
120
+ * it, so attendance is moot.
121
+ * - `unattended` — eligible but NO fresh executor heartbeat (missing,
122
+ * malformed, or older than K×tickMs). This is the
123
+ * fail-closed default: absence of proof ⇒ unattended, so a
124
+ * down/absent supervisor is visible.
125
+ * - `attended` — eligible and a heartbeat exists within K×tickMs.
126
+ */
127
+ export function loopAttendance(root, loop, env = process.env, now = Date.now(), k = DEFAULT_UNATTENDED_TICKS) {
128
+ if (!isLoopAutoTickEligible(loop, env))
129
+ return "not-applicable";
130
+ const hb = readExecutorHeartbeat(root, loop.id);
131
+ if (!hb)
132
+ return "unattended";
133
+ const at = Date.parse(hb.at);
134
+ if (Number.isNaN(at))
135
+ return "unattended";
136
+ const tickMs = loop.policy?.tickMs && loop.policy.tickMs > 0 ? loop.policy.tickMs : FALLBACK_TICK_MS;
137
+ const age = now - at;
138
+ // Fail-closed on an implausibly FUTURE heartbeat: a beat stamped by a skewed
139
+ // or lying holder (`at` far ahead of the reader's clock) must NOT read
140
+ // `attended` forever. One tick of skew tolerance, then unattended.
141
+ if (age < -tickMs)
142
+ return "unattended";
143
+ // Fail-closed on staleness.
144
+ return age > k * tickMs ? "unattended" : "attended";
145
+ }
146
+ function delay(ms, signal) {
147
+ return new Promise((resolve) => {
148
+ if (signal?.aborted)
149
+ return resolve();
150
+ const timer = setTimeout(() => {
151
+ signal?.removeEventListener("abort", onAbort);
152
+ resolve();
153
+ }, ms);
154
+ function onAbort() {
155
+ clearTimeout(timer);
156
+ resolve();
157
+ }
158
+ signal?.addEventListener("abort", onAbort, { once: true });
159
+ });
160
+ }
161
+ /**
162
+ * Run ONE supervisor beat: tick+execute every eligible loop we can lease, in
163
+ * isolation. Exposed so a caller (or a test) can drive a single beat without the
164
+ * timer loop. Never throws: a loop's failure is captured in `errored`.
165
+ */
166
+ export async function runSupervisorBeat(root, options = {}) {
167
+ const env = options.env ?? process.env;
168
+ const nowFn = options.now ?? (() => Date.now());
169
+ const tick = options.runTickFn ?? runTick;
170
+ const holder = options.holder ?? defaultHolder();
171
+ const leaseMs = options.leaseMs ?? DEFAULT_SUPERVISOR_LEASE_MS;
172
+ const tickTimeoutMs = options.tickTimeoutMs ?? Math.floor(leaseMs * TICK_TIMEOUT_FRACTION);
173
+ const ticked = [];
174
+ const skippedLocked = [];
175
+ const errored = [];
176
+ // Kill-switch: freeze the entire beat, tick nothing. Checked here AND inside
177
+ // listAutoTickLoops (redundant-but-safe) so a race that flips the switch
178
+ // BEFORE the list call yields an empty set either way.
179
+ if (autoTickGloballyDisabled(env)) {
180
+ return { ticked, skippedLocked, errored, frozen: true };
181
+ }
182
+ for (const loop of listAutoTickLoops(root, env)) {
183
+ // DRAIN: honor a shutdown signal BETWEEN loops so SIGTERM stops launching new
184
+ // work promptly instead of ticking every remaining eligible loop first.
185
+ if (options.signal?.aborted)
186
+ break;
187
+ const lease = acquireLoopExecutorLease(root, loop.id, { leaseMs, holder });
188
+ if (!lease) {
189
+ skippedLocked.push(loop.id);
190
+ continue;
191
+ }
192
+ // Bound the tick: abort it once tickTimeoutMs elapses (fail-closed) OR the
193
+ // supervisor is shutting down. The engine stops firing new actions on abort.
194
+ const tickAc = new AbortController();
195
+ const onOuterAbort = () => tickAc.abort();
196
+ options.signal?.addEventListener("abort", onOuterAbort, { once: true });
197
+ let timedOut = false;
198
+ const timer = setTimeout(() => {
199
+ timedOut = true;
200
+ tickAc.abort();
201
+ }, tickTimeoutMs);
202
+ try {
203
+ // Race the tick against its own abort so a hung tick never wedges the beat:
204
+ // if it overruns / is cancelled, we stop waiting, mark it errored (no
205
+ // heartbeat → the loop reads `unattended`), and move on.
206
+ await Promise.race([
207
+ tick(root, loop.id, { execute: true, signal: tickAc.signal }),
208
+ new Promise((resolve) => {
209
+ tickAc.signal.addEventListener("abort", () => resolve(), { once: true });
210
+ })
211
+ ]);
212
+ if (tickAc.signal.aborted) {
213
+ errored.push({
214
+ loopId: loop.id,
215
+ error: timedOut ? `tick exceeded ${tickTimeoutMs}ms lease-safety timeout` : "aborted"
216
+ });
217
+ }
218
+ else {
219
+ stampExecutorHeartbeat(root, loop.id, {
220
+ holder,
221
+ fencingToken: lease.token,
222
+ at: nowFn()
223
+ });
224
+ ticked.push(loop.id);
225
+ }
226
+ }
227
+ catch (err) {
228
+ // FAIL-CLOSED ISOLATION: this loop is simply not attended this beat; its
229
+ // heartbeat stays stale → surfaced as `unattended`. The beat continues.
230
+ errored.push({ loopId: loop.id, error: err instanceof Error ? err.message : String(err) });
231
+ }
232
+ finally {
233
+ clearTimeout(timer);
234
+ options.signal?.removeEventListener("abort", onOuterAbort);
235
+ lease.release();
236
+ }
237
+ }
238
+ return { ticked, skippedLocked, errored, frozen: false };
239
+ }
240
+ /**
241
+ * Run the durable supervisor until `signal` aborts (or `max` beats elapse). This
242
+ * is what the systemd `--user` unit runs (Lot 1c). It is NEVER on any default
243
+ * code path — existing users see no change until they explicitly launch it.
244
+ */
245
+ export async function runLoopSupervisor(root, options = {}) {
246
+ const intervalMs = options.intervalMs && options.intervalMs > 0
247
+ ? options.intervalMs
248
+ : DEFAULT_SUPERVISOR_INTERVAL_MS;
249
+ let beats = 0;
250
+ for (;;) {
251
+ const summary = await runSupervisorBeat(root, options);
252
+ await options.onBeat?.(summary);
253
+ beats += 1;
254
+ if (options.max !== undefined && beats >= options.max)
255
+ return beats;
256
+ if (options.signal?.aborted)
257
+ return beats;
258
+ await delay(intervalMs, options.signal);
259
+ if (options.signal?.aborted)
260
+ return beats;
261
+ }
262
+ }
263
+ //# sourceMappingURL=supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../../src/runtime/loop/supervisor.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentropic/h2a",
3
- "version": "0.85.18",
3
+ "version": "0.85.20",
4
4
  "description": "h2a — the unified CLI + core for human-to-agent coordination.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -51,7 +51,7 @@
51
51
  "@hono/mcp": "^0.3.0",
52
52
  "@hono/node-server": "^2.0.4",
53
53
  "@modelcontextprotocol/sdk": "^1.29.0",
54
- "@sentropic/track": "^0.85.18",
54
+ "@sentropic/track": "^0.85.20",
55
55
  "hono": "^4.12.23"
56
56
  },
57
57
  "peerDependencies": {
@@ -70,6 +70,24 @@ established tracking flow; otherwise recommend it without recording it. Never cr
70
70
  duplicate follow-up work from speculation. A handoff closes the diagnosed blocking item; it does not
71
71
  satisfy or defer any remaining explicit objective.
72
72
 
73
+ ## Rebase, never cherry-pick
74
+
75
+ To move committed work between branches, rebase — never cherry-pick. Cherry-pick copies a commit under a
76
+ new SHA, so the same change now exists twice with different identities: history diverges, a later merge or
77
+ rebase sees "different" commits, and the duplicate resurfaces in diffs, reviews, and merge-loss checks.
78
+
79
+ - To retarget a branch onto an updated base, `git rebase <base>` (or `git rebase --onto <newbase> <upstream>`
80
+ to replay only a specific range). The commits keep their logical identity and the branch stays linear.
81
+ - To isolate one feature's commits from an entangled branch, `git rebase --onto origin/main <commit>~1 <branch>`
82
+ replays exactly that range onto the base — a rebase, not a copy.
83
+ - `git cherry-pick` is reserved for the narrow case of applying a change to a branch that will NEVER share
84
+ history with the source (e.g. a backport to a frozen release line). It is not a tool for normal
85
+ branch-to-branch movement, and never a substitute for rebasing a feature onto a fresh base.
86
+
87
+ Rationale: this is the same non-duplication discipline the branch-lifecycle event-containment gate enforces
88
+ for `.track` — a copied commit is a second identity for one change, and every downstream tool then has to
89
+ reconcile two things that should have been one.
90
+
73
91
  ## Discipline first
74
92
 
75
93
  Process skills (`harness/brainstorm`, `harness/debug`) decide HOW to approach the task — invoke them