@aron98/load-lock 0.0.0-stage → 0.1.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -2
  3. package/demo/demo.sh +112 -0
  4. package/demo/sim-agent.mjs +149 -0
  5. package/dist/adapters/opencode/plugin.d.ts +18 -0
  6. package/dist/adapters/opencode/plugin.js +301 -0
  7. package/dist/cli/hook.d.ts +1 -0
  8. package/dist/cli/hook.js +141 -0
  9. package/dist/cli/install.d.ts +14 -0
  10. package/dist/cli/install.js +377 -0
  11. package/dist/cli/main.d.ts +2 -0
  12. package/dist/cli/main.js +461 -0
  13. package/dist/core/daemon-main.d.ts +2 -0
  14. package/dist/core/daemon-main.js +27 -0
  15. package/dist/core/daemon.d.ts +79 -0
  16. package/dist/core/daemon.js +526 -0
  17. package/dist/core/persist.d.ts +11 -0
  18. package/dist/core/persist.js +66 -0
  19. package/dist/core/state.d.ts +203 -0
  20. package/dist/core/state.js +909 -0
  21. package/dist/core/sysinfo.d.ts +2 -0
  22. package/dist/core/sysinfo.js +123 -0
  23. package/dist/mcp/main.d.ts +2 -0
  24. package/dist/mcp/main.js +4 -0
  25. package/dist/mcp/server.d.ts +27 -0
  26. package/dist/mcp/server.js +352 -0
  27. package/dist/policy/text.d.ts +30 -0
  28. package/dist/policy/text.js +58 -0
  29. package/dist/shared/client.d.ts +30 -0
  30. package/dist/shared/client.js +227 -0
  31. package/dist/shared/protocol.d.ts +333 -0
  32. package/dist/shared/protocol.js +22 -0
  33. package/dist/test/core.test.d.ts +1 -0
  34. package/dist/test/core.test.js +281 -0
  35. package/dist/test/daemon.test.d.ts +1 -0
  36. package/dist/test/daemon.test.js +384 -0
  37. package/dist/test/hook.test.d.ts +1 -0
  38. package/dist/test/hook.test.js +126 -0
  39. package/dist/test/mcp.test.d.ts +1 -0
  40. package/dist/test/mcp.test.js +139 -0
  41. package/dist/test/run-helper.test.d.ts +1 -0
  42. package/dist/test/run-helper.test.js +84 -0
  43. package/dist/test/testutil.d.ts +27 -0
  44. package/dist/test/testutil.js +111 -0
  45. package/docs/ADAPTERS.md +118 -0
  46. package/docs/AGENT_GUIDE.md +142 -0
  47. package/docs/ARCHITECTURE.md +195 -0
  48. package/docs/COMPATIBILITY.md +116 -0
  49. package/docs/OPERATIONS.md +126 -0
  50. package/package.json +102 -4
  51. package/skills/load-lock/SKILL.md +73 -0
package/package.json CHANGED
@@ -1,6 +1,104 @@
1
1
  {
2
2
  "name": "@aron98/load-lock",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Machine-wide counting semaphore coordinating resource-intensive work across parallel coding agents (OpenCode, Codex CLI, Claude Code, ...)",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/aron98/load-lock.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/aron98/load-lock/issues"
13
+ },
14
+ "homepage": "https://github.com/aron98/load-lock#readme",
15
+ "keywords": [
16
+ "coding-agent",
17
+ "agent-coordination",
18
+ "mcp",
19
+ "opencode",
20
+ "codex",
21
+ "claude-code",
22
+ "semaphore",
23
+ "resource-management",
24
+ "build"
25
+ ],
26
+ "bin": {
27
+ "load-lock": "dist/cli/main.js",
28
+ "load-lock-mcp": "dist/mcp/main.js"
29
+ },
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/adapters/opencode/plugin.d.ts",
33
+ "default": "./dist/adapters/opencode/plugin.js"
34
+ },
35
+ "./shared/client": {
36
+ "types": "./dist/shared/client.d.ts",
37
+ "default": "./dist/shared/client.js"
38
+ },
39
+ "./shared/client.js": {
40
+ "types": "./dist/shared/client.d.ts",
41
+ "default": "./dist/shared/client.js"
42
+ },
43
+ "./shared/protocol": {
44
+ "types": "./dist/shared/protocol.d.ts",
45
+ "default": "./dist/shared/protocol.js"
46
+ },
47
+ "./shared/protocol.js": {
48
+ "types": "./dist/shared/protocol.d.ts",
49
+ "default": "./dist/shared/protocol.js"
50
+ },
51
+ "./policy/text": {
52
+ "types": "./dist/policy/text.d.ts",
53
+ "default": "./dist/policy/text.js"
54
+ },
55
+ "./policy/text.js": {
56
+ "types": "./dist/policy/text.d.ts",
57
+ "default": "./dist/policy/text.js"
58
+ },
59
+ "./mcp/server": {
60
+ "types": "./dist/mcp/server.d.ts",
61
+ "default": "./dist/mcp/server.js"
62
+ },
63
+ "./mcp/server.js": {
64
+ "types": "./dist/mcp/server.d.ts",
65
+ "default": "./dist/mcp/server.js"
66
+ },
67
+ "./core/daemon": {
68
+ "types": "./dist/core/daemon.d.ts",
69
+ "default": "./dist/core/daemon.js"
70
+ },
71
+ "./core/daemon.js": {
72
+ "types": "./dist/core/daemon.d.ts",
73
+ "default": "./dist/core/daemon.js"
74
+ },
75
+ "./dist/*": "./dist/*"
76
+ },
77
+ "files": [
78
+ "dist",
79
+ "docs",
80
+ "skills",
81
+ "demo",
82
+ "README.md"
83
+ ],
84
+ "scripts": {
85
+ "build": "tsc -p tsconfig.json",
86
+ "test": "tsc -p tsconfig.json && node --test dist/test/*.test.js",
87
+ "typecheck": "tsc -p tsconfig.json --noEmit"
88
+ },
89
+ "engines": {
90
+ "node": ">=20"
91
+ },
92
+ "publishConfig": {
93
+ "access": "public"
94
+ },
95
+ "dependencies": {
96
+ "@modelcontextprotocol/sdk": "^1.32.1",
97
+ "@opencode/plugin": "^2.0.29",
98
+ "zod": "^3.24.0"
99
+ },
100
+ "devDependencies": {
101
+ "@types/node": "^24.0.0",
102
+ "typescript": "^5.9.0"
103
+ }
104
+ }
@@ -0,0 +1,73 @@
1
+ # load-lock: machine-wide heavy-work coordination
2
+
3
+ All coding agents on this machine (any harness, any repo/worktree) share one
4
+ small budget of heavy-work permits. Before resource-intensive local work, a
5
+ polite agent acquires a permit; everyone else keeps doing light work until an
6
+ offer arrives.
7
+
8
+ ## When to use it
9
+
10
+ **Heavy** (acquire first): full builds, `cargo build`/`npm run build`, whole
11
+ test suites, codegen over the whole repo, bulk compilation, anything that
12
+ pins several cores or gobs of RAM for minutes.
13
+
14
+ **Light** (no permit): file edits, greps, small targeted tests, single-file
15
+ checks, reading, planning, git operations, anything sub-second or trivial.
16
+
17
+ Judge by *expected* CPU/memory/IO impact, not by command name. When unsure,
18
+ `inspect_machine` shows real capacity (incl. container limits) and pressure.
19
+
20
+ ## Core flow
21
+
22
+ 1. `acquire(reason, op_key)` — idempotent per `op_key`. Result is either
23
+ `granted` (you hold a permit; start heavy work now) or `queued`
24
+ (keep doing light work; an offer will arrive).
25
+ 2. While queued: **continue useful light work**. Do not idle-poll.
26
+ 3. When an offer arrives (harness wakeup or `pending`): `claim(offer_token)`
27
+ inside the claim window (default 30s). Expired offers are rejected; your
28
+ request pauses until you `rejoin`.
29
+ 4. Only after `granted`/`claim` succeeds, start the heavy work. Associate the
30
+ work with the permit:
31
+ - `run_with_permit(permit_token, command)` — best: spawns it, tracks the
32
+ process group, auto-releases on exit. Poll `job_status`; do NOT release
33
+ manually.
34
+ - `attach_pid(permit_token, pid)` — for a process you already started
35
+ (e.g. a background shell task). Auto-release when it exits.
36
+ - `mark_started(permit_token)` — last resort, no process association
37
+ (heartbeat lease applies instead).
38
+ 5. `release(permit_token)` when done (idempotent), or `cancel(request_id)`
39
+ for queue entries you no longer need. Background jobs that outlive your
40
+ session keep their permit until the *process* exits — never release just
41
+ because a tool call returned a background-task id.
42
+
43
+ ## Cleanup rules (why you still see permits released)
44
+
45
+ - Offers expire (short window) and never auto-recycle: a missed offer pauses
46
+ your request; `rejoin(request_id)` puts you back in the FIFO queue.
47
+ - Claimed-but-never-started permits are reclaimed at the start deadline
48
+ (default 120s) so a forgotten claim can't block the queue.
49
+ - Tracked jobs (run_with_permit/attach_pid) hold the permit exactly as long
50
+ as the process (group) lives; completion/failure releases automatically.
51
+ - Untracked (mark_started) permits are held under a heartbeat lease — if your
52
+ harness adapter disappears without a trace of the work, the lease reclaims
53
+ the permit. The tradeoff: a still-running but unobserved process could
54
+ overlap with new work. Prefer tracking.
55
+ - Never auto-released merely for being held a long time, if the work is
56
+ confirmed running.
57
+
58
+ ## Machine awareness and config
59
+
60
+ - `inspect_machine` — cores, memory, cgroup/container limits, current pressure.
61
+ - `get_config` / `configure(expected_version, changes, reason)` — compare-and-swap
62
+ capacity and timing changes; read the version first, always explain why.
63
+ - `status` — machine-wide snapshot: holders, offers, queue.
64
+ - Anyone may adjust the budget; changes are persisted with rationale+author.
65
+
66
+ ## Recovery
67
+
68
+ - `pending` — your own offers/queue/holds (check before giving up).
69
+ - `load-lock status` / `load-lock recover list` (human CLI) shows orphaned
70
+ permits with process liveness; `load-lock recover force-release --id` is the
71
+ explicit escape hatch (it prints holder/job info first).
72
+ - Daemon restart: running tracked permits survive (journaled); queued/offered
73
+ requests do not — re-`acquire`.