@hasna/loops 0.3.59 → 0.4.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 (56) hide show
  1. package/CHANGELOG.md +247 -0
  2. package/LICENSE +197 -13
  3. package/README.md +28 -60
  4. package/dist/cli/index.js +7269 -5776
  5. package/dist/daemon/control.d.ts +21 -1
  6. package/dist/daemon/daemon.d.ts +5 -0
  7. package/dist/daemon/index.js +2985 -1072
  8. package/dist/daemon/install.d.ts +1 -1
  9. package/dist/index.d.ts +21 -8
  10. package/dist/index.js +5906 -4580
  11. package/dist/lib/accounts.d.ts +6 -1
  12. package/dist/lib/agent-adapter.d.ts +74 -0
  13. package/dist/lib/backup.d.ts +25 -0
  14. package/dist/lib/errors.d.ts +21 -0
  15. package/dist/lib/executor.d.ts +10 -1
  16. package/dist/lib/goal/metadata.d.ts +16 -0
  17. package/dist/lib/goal/model-factory.d.ts +1 -0
  18. package/dist/lib/goal/prompts.d.ts +3 -1
  19. package/dist/lib/goal/types.d.ts +3 -0
  20. package/dist/lib/health.d.ts +1 -1
  21. package/dist/lib/ids.d.ts +8 -1
  22. package/dist/lib/machines.d.ts +6 -0
  23. package/dist/lib/process-identity.d.ts +16 -0
  24. package/dist/lib/{schedule.d.ts → recurrence.d.ts} +1 -0
  25. package/dist/lib/redact.d.ts +21 -0
  26. package/dist/lib/route/cursors.d.ts +18 -0
  27. package/dist/lib/route/drain.d.ts +6 -0
  28. package/dist/lib/route/fields.d.ts +27 -0
  29. package/dist/lib/route/gates.d.ts +26 -0
  30. package/dist/lib/route/index.d.ts +18 -0
  31. package/dist/lib/route/options.d.ts +31 -0
  32. package/dist/lib/route/parse.d.ts +8 -0
  33. package/dist/lib/route/pr-review.d.ts +11 -0
  34. package/dist/lib/route/provider.d.ts +41 -0
  35. package/dist/lib/route/route-event.d.ts +23 -0
  36. package/dist/lib/route/route-tasks.d.ts +75 -0
  37. package/dist/lib/route/throttle.d.ts +47 -0
  38. package/dist/lib/route/todos-cli.d.ts +21 -0
  39. package/dist/lib/route/types.d.ts +88 -0
  40. package/dist/lib/run-artifacts.d.ts +17 -2
  41. package/dist/lib/run-envelope.d.ts +41 -0
  42. package/dist/lib/scheduler.d.ts +78 -2
  43. package/dist/lib/store.d.ts +63 -0
  44. package/dist/lib/store.js +1011 -266
  45. package/dist/lib/template-kit.d.ts +70 -0
  46. package/dist/lib/templates-custom.d.ts +34 -0
  47. package/dist/lib/templates.d.ts +13 -81
  48. package/dist/lib/workflow-runner.d.ts +2 -0
  49. package/dist/mcp/index.d.ts +3 -0
  50. package/dist/mcp/index.js +3313 -1165
  51. package/dist/sdk/index.d.ts +29 -3
  52. package/dist/sdk/index.js +2910 -897
  53. package/dist/test-helpers.d.ts +37 -0
  54. package/dist/types.d.ts +2 -0
  55. package/docs/USAGE.md +40 -18
  56. package/package.json +6 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,247 @@
1
+ # Changelog
2
+
3
+ All notable changes to OpenLoops (npm `@hasna/loops`, repo `hasna/loops`) are
4
+ documented in this file. Version entries are generated from the
5
+ conventional-commit git history; one commit maps to one released patch version
6
+ unless noted.
7
+
8
+ ## 0.4.0 (2026-07-02)
9
+
10
+ Audit-hardening release: write-path secret scrubbing, process-group reaping,
11
+ gated schema migrations, storage garbage collection, and a consolidated
12
+ CLI/MCP/SDK surface with deprecation aliases.
13
+
14
+ ### Security
15
+
16
+ - Write-path secret scrubbing (`src/lib/redact.ts`): persisted run/step
17
+ errors, goal evidence, raw model responses, and run envelopes pass through
18
+ `scrubSecrets`/`scrubSecretsDeep` before storage. Recognized credential
19
+ shapes — Anthropic `sk-ant-*`, OpenAI `sk-proj-*`, AWS `AKIA*`, GitHub
20
+ `ghp_*`/`github_pat_*`, Slack `xox?-*`, PEM private key blocks, and generic
21
+ `KEY="..."` assignments with high-entropy values — are replaced with
22
+ `[SCRUBBED]`. Scrubbing is idempotent and bounded for large (256KB+)
23
+ payloads.
24
+ - MCP shell removal: `loops_create_command` now takes a structured
25
+ `command` + `args` argv only. The former `shell` boolean is rejected
26
+ (schema `z.never()`) instead of being silently stripped — shell loops
27
+ remain a human decision via the CLI. Mutation tools stay double-gated
28
+ behind `LOOPS_MCP_ALLOW_MUTATIONS=true` plus per-call confirmation.
29
+ - Archived-loop guards centralized in the store: `updateLoop` throws a coded
30
+ `LoopArchivedError` for archived loops (except the explicit unarchive
31
+ path), so CLI, MCP, SDK, daemon, and scheduler all share one enforcement
32
+ point instead of per-surface checks.
33
+ - Executor-enforced git worktrees: when a target carries worktree metadata,
34
+ the executor prepares/verifies and enters the worktree before spawning the
35
+ child (locally and on remote machine dispatch), records the entered
36
+ worktree, and `worktreeMode=required` fails closed instead of falling back
37
+ to the original checkout.
38
+ - Coded error classes (`src/lib/errors.ts`): `LoopNotFoundError`,
39
+ `LoopArchivedError`, `AmbiguousNameError`, `ValidationError`, each with a
40
+ stable `.code` so callers branch on codes instead of message text.
41
+
42
+ ### Reliability
43
+
44
+ - Run lease re-acquisition: runners heartbeat their lease
45
+ (`heartbeatRunLease`, every `leaseMs/3` capped at 60s) and the store
46
+ records the child process identity (`recordRunProcess` with pid/pgid/
47
+ process start fingerprint) so recovery can tell live runs from dead ones.
48
+ - Orphan reaping: after startup and periodic lease recovery, the daemon
49
+ signals the process groups of abandoned runs (SIGTERM, then SIGKILL after
50
+ a grace period) using the pid/pgid + start-time fingerprints returned by
51
+ `recoverExpiredRunLeases`; unfingerprinted pids are never trusted or
52
+ signaled.
53
+ - Retry backoff with jitter: exponential
54
+ `retryDelayMs * 2^(attempt-1) * (0.5 + random)`, capped at 6h;
55
+ rate-limit/auth failures back off 4x harder.
56
+ - Circuit breaker: after 5 consecutive final failures (default,
57
+ configurable/disableable) the loop auto-pauses with a health-visible
58
+ skipped marker run explaining the trip; `loops resume` re-arms it and
59
+ requires a fresh failure streak before tripping again.
60
+ - Default idle watchdog for agent targets: agent runs that set neither
61
+ `timeoutMs` nor `idleTimeoutMs` now time out after 30 minutes without
62
+ observable progress (4 hours for buffered agents); override with
63
+ `idleTimeoutMs` or `LOOPS_AGENT_IDLE_TIMEOUT_MS`. Timeouts kill the
64
+ child's entire process group, not just the direct child.
65
+ - Daemon logging: every line is timestamped `[ISO8601] [loops-daemon] ...`,
66
+ and `daemon.log` rotates at 50MB.
67
+
68
+ ### Storage
69
+
70
+ - Gated schema migrations: migrations are tracked in `schema_migrations` and
71
+ the database stamps `PRAGMA user_version` (now 6). Older 0.4.x+ binaries
72
+ refuse to open a newer database instead of silently misreading it;
73
+ baseline 0.3.x migrations are re-applied idempotently to converge drifted
74
+ databases (including the known live fork with orphan
75
+ `loops.metadata_json`/`loop_runs.source` columns).
76
+ - `Store.pruneHistory({ maxAgeDays?, keepPerLoop?, dryRun? })` deletes old
77
+ terminal run history in bounded batches and returns a deletion summary;
78
+ exposed as the new `loops gc` command (dry-run by default, `--apply` to
79
+ execute) which also rotates database backups, checkpoints the WAL, and
80
+ removes stray temp files.
81
+ - Online backups via `VACUUM INTO` (`src/lib/backup.ts`):
82
+ `backupDatabase({ reason, keep = 3 })` with a per-reason 1h debounce and
83
+ retention pruning; destructive CLI operations (rename, name-hygiene apply)
84
+ snapshot the database first.
85
+ - Bounded run envelopes (`src/lib/run-envelope.ts`): persisted stdout/stderr
86
+ excerpts are capped (2048 chars per excerpt) and scrubbed, keeping run
87
+ rows small and secret-free.
88
+ - Time-sortable ids: `genId()` now returns a 128-bit ULID-like id — 48-bit
89
+ millisecond timestamp prefix + 80 random bits, 32 lowercase hex chars —
90
+ so primary keys sort by creation time while staying compatible with
91
+ existing TEXT keys.
92
+
93
+ ### CLI / MCP / SDK
94
+
95
+ - CLI: new `loops gc`; `routes` is the canonical event-routing surface.
96
+ Deprecated aliases retained for one release cycle: `loops events
97
+ handle|drain ...` (use `loops routes create|drain`), `loops templates
98
+ create` (use `loops workflows create --template <id>`), and `loops goal
99
+ status` (merged into `loops goal show`). Internal debloat consolidated
100
+ ~1,900 lines of template code and moved route/template plumbing into
101
+ `src/lib/route/` and `src/lib/template-kit.ts` without removing any
102
+ non-deprecated command.
103
+ - MCP: tools renamed to a canonical `loops_*` namespace (e.g. `loop_runs` →
104
+ `loops_runs`, `workflow_read` → `loops_workflow_read`); every legacy name
105
+ is still registered as a deprecated alias. New read-only tools:
106
+ `loops_health`, `loops_diagnose`, `loops_daemon_status`,
107
+ `loops_workflow_run_inspect`; new guarded mutations: `loops_stop`,
108
+ `loops_archive`, `loops_unarchive`. A golden-schema test
109
+ (`src/mcp/golden-schema.test.ts`) pins the exported tool schemas.
110
+ - SDK: `LoopsClient.list()` accepts status/limit/archived filters;
111
+ `runs(idOrName?, { status?, limit? })` resolves names and returns `[]` for
112
+ unknown loops (v0.3.x-compatible polling); new `doctor()` and `health()`
113
+ reports; `pause`/`resume`/`stop` surface the store's coded
114
+ `LoopArchivedError`.
115
+ - Root export (`@hasna/loops`) is now a curated, documented API surface
116
+ (SDK client, MCP factory, coded errors, domain types, doctor/health
117
+ helpers) instead of `export *` of internals.
118
+ - Packaging: `@hasna/machines` moved to `optionalDependencies`; `prepare`
119
+ only builds when `dist/` is missing; `CHANGELOG.md` ships in the npm
120
+ package; full Apache-2.0 license text restored in `LICENSE`; CI workflow
121
+ added (`.github/workflows/ci.yml`).
122
+
123
+ ### BREAKING / UPGRADE NOTES
124
+
125
+ - **Database version stamp — plan downgrades before upgrading.** The first
126
+ 0.4.0 process to open a loops database applies migration 0006 and stamps
127
+ `PRAGMA user_version = 6`. Migrations are additive (no columns dropped),
128
+ and 0.3.x binaries do not check `user_version`, so rollback generally
129
+ works — but downgrading after 0.4.0 has written process-identity data is
130
+ unsupported and untested. Take a copy of `loops.db` (or rely on the
131
+ automatic `VACUUM INTO` backups) before upgrading shared machines.
132
+ - **Root import surface narrowed.** `@hasna/loops` no longer re-exports
133
+ every internal symbol. If you imported undocumented internals from the
134
+ package root, switch to the curated exports or the `./sdk`, `./mcp`, or
135
+ `./storage` subpaths. `./storage` (raw `Store`) remains internal plumbing
136
+ and may change without notice.
137
+ - **Coded errors replace message-matching.** SDK/store mutations on
138
+ archived or missing loops now throw `LoopArchivedError` /
139
+ `LoopNotFoundError` (with `.code`) instead of plain `Error`s with ad-hoc
140
+ messages. Update any `error.message.includes(...)` checks.
141
+ - **`LoopsClient.runs()` signature changed** from `runs(loopId?)` to
142
+ `runs(idOrName?, filters?)`; it now resolves loop names and returns `[]`
143
+ when the loop does not exist.
144
+ - **`genId()` no longer takes a length argument** and always returns 32
145
+ chars. Existing shorter ids remain valid; code that assumed 12-char ids
146
+ must not.
147
+ - **MCP `loop_create_command` requests that include `shell` are rejected**
148
+ with a validation error instead of the flag being ignored. Remove the
149
+ field and pass argv-style `command` + `args`.
150
+ - **MCP tool names changed** to `loops_*`; legacy names (`loop_runs`,
151
+ `loop_pause`, `workflow_read`, ...) still work as deprecated aliases but
152
+ will be removed in a future minor. Re-list tools and migrate callers.
153
+ - **CLI deprecations** (aliases still work, removal planned): `loops events
154
+ handle|drain` → `loops routes create|drain`; `loops templates create` →
155
+ `loops workflows create --template <id>`; `loops goal status` → `loops
156
+ goal show`.
157
+ - **Agent loops can now time out by default.** Previously an agent target
158
+ with no `timeoutMs` could hang forever; it now idle-times-out after 30
159
+ minutes without progress (4h for buffered agents). Long-running agents
160
+ must set `timeoutMs`/`idleTimeoutMs` explicitly or export
161
+ `LOOPS_AGENT_IDLE_TIMEOUT_MS`.
162
+ - **The daemon reaps abandoned process groups.** Inline/external runners
163
+ must keep the `<surface>:<pid>` runner-id shape and record process
164
+ identity through the scheduler so a starting daemon can see the owner is
165
+ alive; runs claimed without fingerprints are re-queued on lease expiry
166
+ (their processes are never signaled without a verified fingerprint).
167
+ - **Failing loops auto-pause.** After 5 consecutive final failures the
168
+ circuit breaker pauses the loop with a `circuit breaker open` marker run;
169
+ fix the cause and `loops resume` the loop. Monitoring that expects
170
+ endless retries should watch for these markers.
171
+ - **Bun is the only supported runtime** (`bun >= 1.0` on PATH even under
172
+ npm installs), and `@hasna/machines` is now an optional dependency —
173
+ installs without it simply disable remote-machine assignment.
174
+
175
+ ## 0.3.x
176
+
177
+ Compact history for the 0.3 line, newest first (`version (date) commit subject`).
178
+
179
+ - 0.3.60 (2026-07-01) fix: run Codewith loops as durable agents
180
+ - 0.3.59 (2026-07-01) fix: harden workflow goal migration
181
+ - 0.3.58 (2026-07-01) fix: avoid nested workflow goal deadlocks
182
+ - 0.3.57 (2026-07-01) feat: harden loop routing and add MCP server
183
+ - 0.3.56 (2026-06-30) fix: allow worktree agents to write git metadata
184
+ - 0.3.55 (2026-06-30) fix: agent workflow timeout policy
185
+ - 0.3.54 (2026-06-29) feat: add prompt-file support for agent loops
186
+ - 0.3.53 (2026-06-29) fix: refresh route invocation metadata safely
187
+ - 0.3.52 (2026-06-29) feat: add full task lifecycle route template
188
+ - 0.3.51 (2026-06-29) fix: harden custom template registry
189
+ - 0.3.50 (2026-06-29) feat: add custom templates and loop rename
190
+ - 0.3.49 (2026-06-29) fix: harden workflow route lifecycle
191
+ - 0.3.48 (2026-06-29) fix: harden loop routing and recovery
192
+ - 0.3.47 (2026-06-29) fix: validate agent loop options before storing
193
+ - 0.3.46 (2026-06-29) fix: harden generic and cursor agent routes
194
+ - 0.3.45 (2026-06-29) fix: allow routed task workflows to update app stores
195
+ - 0.3.44 (2026-06-29) fix: harden loop readiness adapters
196
+ - 0.3.43 (2026-06-28) fix: refresh stale generated route workflows
197
+ - 0.3.42 (2026-06-28) chore: refresh loops cli release
198
+ - 0.3.41 (2026-06-28) fix: give routed task agents exact todos project commands
199
+ - 0.3.40 (2026-06-28) fix: migrate workflow run invocation indexes safely
200
+ - 0.3.39 (2026-06-28) chore: release loops 0.3.39
201
+ - 0.3.38 (2026-06-28) docs: require worktrees for routed repo tasks
202
+ - 0.3.37 (2026-06-28) docs: require worktrees in routed examples
203
+ - 0.3.36 (2026-06-28) fix: treat configured project path as fallback
204
+ - 0.3.35 (2026-06-28) fix: prefer task repository paths for routing
205
+ - 0.3.34 (2026-06-28) fix: infer task repo paths during drain
206
+ - 0.3.33 (2026-06-28) feat: compact todos drain output
207
+ - 0.3.32 (2026-06-28) fix: read todos drain queues from file
208
+ - 0.3.31 (2026-06-28) fix: handle large todos drain queues
209
+ - 0.3.30 (2026-06-28) fix: dedupe task routes across prefixes
210
+ - 0.3.29 (2026-06-28) feat: filter drained tasks by project path
211
+ - 0.3.28 (2026-06-28) feat: drain ready todos task workflows
212
+ - 0.3.27 (2026-06-28) feat: throttle task event workflows
213
+ - 0.3.26 (2026-06-27) feat: add safe auto routing for loop tasks (includes Cursor Agent CLI adapter fix, PR #10)
214
+ - 0.3.25 (2026-06-27) fix: redact routed health evidence
215
+ - 0.3.24 (2026-06-27) feat: add runtime loop preflight
216
+ - 0.3.23 (2026-06-27) feat: preflight event workflows
217
+ - 0.3.22 (2026-06-27) feat: preflight loop creation
218
+ - 0.3.21 (2026-06-27) fix: rotate routed loop findings and tighten task routing
219
+ - 0.3.20 (2026-06-27) feat: route loop hygiene findings to todos
220
+ - 0.3.19 (2026-06-27) fix: back up loop database before name hygiene apply
221
+ - 0.3.18 (2026-06-27) fix: harden loop hygiene routing gates
222
+ - 0.3.17 (2026-06-27) feat: add loop health and hygiene abstractions
223
+ - 0.3.16 (2026-06-27) chore: release loops 0.3.16 (includes gated todos task routing eligibility and loop health expectations)
224
+ - 0.3.15 (2026-06-27) feat: archive loops and route task events through account pools
225
+ - 0.3.14 (2026-06-26) chore: release loops 0.3.14 (includes deduped todos task event routing)
226
+ - 0.3.13 (2026-06-26) feat: event-driven workflow templates
227
+ - 0.3.12 (2026-06-25) fix: invoke Cursor agent subcommand
228
+ - 0.3.11 (2026-06-25) chore: release 0.3.11 (includes fix(store): make additive column migrations idempotent)
229
+ - 0.3.10 (2026-06-24) feat: run daemon loop jobs concurrently
230
+ - 0.3.9 (2026-06-24) fix: strict goal response schemas
231
+ - 0.3.8 (2026-06-22) feat: derive CLI version from package metadata
232
+ - 0.3.7 (2026-06-22) feat: add transcript-driven loop workflow
233
+ - 0.3.6 (2026-06-21) feat: add AI SDK goal orchestration
234
+ - 0.3.5 (2026-06-20) fix: avoid login shell for remote machine loops
235
+ - 0.3.4 (2026-06-20) feat: add OpenMachines loop assignment
236
+ - 0.3.3 (2026-06-20) fix: harden OpenLoops daemon ownership and redaction
237
+ - 0.3.2 (2026-06-19) feat: add codewith auth profiles and run-now exit codes
238
+ - 0.3.1 (2026-06-19) fix: loops daemon path and output bugs
239
+ - 0.3.0 (2026-06-19) release: loops workflow hardening
240
+
241
+ ## 0.2.0 (2026-06-19)
242
+
243
+ - feat: add workflows and account-routed execution
244
+
245
+ ## 0.1.0 (2026-06-19)
246
+
247
+ - feat: build OpenLoops CLI daemon
package/LICENSE CHANGED
@@ -1,17 +1,201 @@
1
- Apache License
2
- Version 2.0, January 2004
3
- https://www.apache.org/licenses/
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
4
 
5
- Copyright 2026 Hasna
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
6
 
7
- Licensed under the Apache License, Version 2.0 (the "License");
8
- you may not use this file except in compliance with the License.
9
- You may obtain a copy of the License at
7
+ 1. Definitions.
10
8
 
11
- https://www.apache.org/licenses/LICENSE-2.0
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
12
11
 
13
- Unless required by applicable law or agreed to in writing, software
14
- distributed under the License is distributed on an "AS IS" BASIS,
15
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- See the License for the specific language governing permissions and
17
- limitations under the License.
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 Hasna
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md CHANGED
@@ -2,18 +2,28 @@
2
2
 
3
3
  OpenLoops is a local CLI and daemon for persistent loops and workflows: scheduled or recurring work that survives process restarts and records every run.
4
4
 
5
+ Naming: the product is **OpenLoops**, published on npm as
6
+ [`@hasna/loops`](https://www.npmjs.com/package/@hasna/loops) and developed in the
7
+ [`hasna/loops`](https://github.com/hasna/loops) repository. The installed
8
+ binaries are `loops`, `loops-daemon`, and `loops-mcp`.
9
+
5
10
  It supports deterministic command loops, JSON-defined workflows, and guarded CLI adapters for headless coding agents:
6
11
 
7
12
  - `claude`
8
13
  - `agent` (Cursor Agent CLI)
9
- - `codewith exec`
14
+ - `codewith agent start`
10
15
  - `aicopilot run`
11
16
  - `opencode run`
12
17
  - `codex exec`
13
18
 
14
19
  ## Install
15
20
 
16
- From npm:
21
+ **OpenLoops requires the [Bun](https://bun.sh) runtime (`bun >= 1.0`).** The
22
+ `loops`, `loops-daemon`, and `loops-mcp` binaries run with a `#!/usr/bin/env bun`
23
+ shebang, so Bun must be on `PATH` even when the package is installed with npm.
24
+ Node.js is not a supported runtime.
25
+
26
+ From npm (with Bun installed):
17
27
 
18
28
  ```bash
19
29
  npm install -g @hasna/loops
@@ -197,7 +207,9 @@ non-secret routing/configuration data.
197
207
 
198
208
  ## Goals
199
209
 
200
- Add `--goal` to wrap a command, agent, or workflow loop in an AI-SDK orchestration layer. OpenLoops asks the configured model to create a flat DAG plan, executes ready nodes by calling the underlying target, then runs an adversarial achievement audit before marking the goal complete.
210
+ Add `--goal` to wrap a command, agent, or workflow loop in an AI-SDK orchestration layer. OpenLoops asks the configured model to create a flat DAG plan, then executes ready plan nodes by re-running the underlying target once per node. Each node run is parameterized with that node's objective: agent prompts get the goal and node objective appended, and every wrapped process receives `LOOPS_GOAL_NODE_KEY` and `LOOPS_GOAL_NODE_OBJECTIVE` in its environment. An adversarial achievement audit runs before the goal is marked complete.
211
+
212
+ The goal `autoExecute` mode is honored: `off` plans and persists the DAG without executing any nodes (the run reports the persisted plan), while `readyOnly` (the default) and `aiDirected` run the dependency-driven execution loop over ready nodes.
201
213
 
202
214
  ```bash
203
215
  export OPENROUTER_API_KEY=...
@@ -213,7 +225,7 @@ loops create agent repo-fixer \
213
225
  --goal-max-turns 5
214
226
  ```
215
227
 
216
- Goal planning and validation use the Vercel AI SDK with `@openrouter/ai-sdk-provider`. Set `OPENROUTER_API_KEY`; optionally set `LOOPS_GOAL_BASE_URL` to point at a local gateway compatible with OpenRouter. Goal context is passed to wrapped commands and agents as `LOOPS_GOAL_ID`, `LOOPS_GOAL_OBJECTIVE`, and `LOOPS_GOAL_NODE_KEY`.
228
+ Goal planning and validation use the Vercel AI SDK with `@openrouter/ai-sdk-provider`. Set `OPENROUTER_API_KEY`; optionally set `LOOPS_GOAL_BASE_URL` to point at a local gateway compatible with OpenRouter. Goal context is passed to wrapped commands and agents as `LOOPS_GOAL_ID`, `LOOPS_GOAL_OBJECTIVE`, `LOOPS_GOAL_NODE_KEY`, and `LOOPS_GOAL_NODE_OBJECTIVE`.
217
229
 
218
230
  If a resumed goal plan has no runnable nodes, failed run output includes a
219
231
  structured `diagnostics` object with the concrete blocker and `owner`
@@ -660,59 +672,15 @@ write OpenLoops SQLite rows directly. The stable contract should be an
660
672
  idempotent CLI/SDK upsert that accepts a fully rendered one-shot workflow loop
661
673
  request and returns durable refs.
662
674
 
663
- Proposed SDK shape:
675
+ The canonical `WorkflowUpsertRequest` / `WorkflowUpsertResult` shapes and their
676
+ required semantics (dry-run/preflight/commit modes, `idempotencyKey` +
677
+ `specHash` idempotency, dispatch behavior, and redaction-before-persistence)
678
+ are specified in `docs/AUTOMATION_RUNTIME_DESIGN.md`; this README intentionally
679
+ does not duplicate that spec.
664
680
 
665
- ```ts
666
- type WorkflowUpsertRequest = {
667
- idempotencyKey: string;
668
- source: { kind: "action" | "automation" | "event"; id: string; dedupeKey?: string };
669
- subject: { kind: "repo" | "task" | "pr" | "run"; id?: string; path?: string; url?: string };
670
- workflow: { name: string; description?: string; steps: WorkflowStepInput[] };
671
- loop: { name: string; schedule: { type: "once"; at: string }; machine?: LoopMachineRef };
672
- route?: { projectPath?: string; projectGroup?: string; concurrencyGroup?: string };
673
- execution?: AutomationExecutionPolicy;
674
- mode?: "dry-run" | "preflight" | "commit";
675
- dispatch?: "schedule" | "run-now" | "none";
676
- };
677
-
678
- type WorkflowUpsertResult = {
679
- ok: boolean;
680
- dryRun: boolean;
681
- idempotencyKey: string;
682
- specHash: string;
683
- refs: {
684
- workflowId?: string;
685
- loopId?: string;
686
- invocationId?: string;
687
- workItemId?: string;
688
- runId?: string;
689
- manifestPath?: string;
690
- };
691
- action: "created" | "updated" | "reused" | "rejected";
692
- preflight?: { ok: boolean; checks: unknown[]; error?: string };
693
- };
694
- ```
695
-
696
- Required semantics:
697
-
698
- - `mode="dry-run"` validates, canonicalizes, hashes, and returns the same JSON
699
- shape without mutating OpenLoops state.
700
- - `mode="preflight"` additionally checks provider binaries, machine routing,
701
- accounts/auth profiles, prompt files, and workflow target compatibility before
702
- commit.
703
- - `mode="commit"` is idempotent on `idempotencyKey` plus `specHash`: identical
704
- requests return existing refs; changed specs create a new workflow version or
705
- one-shot loop while preserving previous run history.
706
- - `dispatch="schedule"` stores a one-shot loop for the daemon; `run-now` claims
707
- an immediate manual slot; `none` only materializes refs for another owner to
708
- trigger later.
709
- - All persisted output is redacted before storage, and returned refs are enough
710
- for the caller to inspect, cancel, replay, or resolve the run without querying
711
- SQLite directly.
712
-
713
- See `docs/AUTOMATION_RUNTIME_DESIGN.md` for the planned DLQ/dead-letter
714
- lifecycle, including `loops dlq list/show/replay/resolve`, idempotent replay
715
- keys, and compatibility rules for `@hasna/actions`.
681
+ The same design doc covers the planned DLQ/dead-letter lifecycle, including
682
+ `loops dlq list/show/replay/resolve`, idempotent replay keys, and compatibility
683
+ rules for `@hasna/actions`.
716
684
 
717
685
  The same design doc also defines the planned strict automation execution mode:
718
686
  minimal env inheritance, scoped secret refs, enforced allowlists,
@@ -867,16 +835,16 @@ On Linux this writes a user systemd service. On macOS it writes a LaunchAgent pl
867
835
  The adapters intentionally use provider command surfaces instead of pretending every agent has one SDK:
868
836
 
869
837
  - Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
870
- - Codewith uses `codewith --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories.
838
+ - Codewith uses durable `codewith --ask-for-approval never agent start` background-agent runs by default, then polls `codewith agent read` until the run is terminal and records compact status/event evidence. Remote Codewith agent steps fail closed until remote durable readback is implemented, so workflows do not advance immediately after enqueue. OpenLoops rejects Codewith `extraArgs` that try to force `exec`, `--ephemeral`, or other non-durable exec-only flags for task-lifecycle, planner, worker, verifier, reviewer, release, or other long-running agentic work.
871
839
  - AI Copilot and OpenCode use `run --format json --pure`. OpenCode requires an explicit provider/model id because ambient OpenCode config is machine-specific.
872
840
  - Cursor is CLI-first for now via the standalone `agent -p` binary. OpenLoops no longer falls back to `cursor agent`; install the standalone Cursor Agent CLI so preflight and scheduled runs use the same executable.
873
841
  - Codex uses `codex --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
874
- - Agent prompts are sent through child stdin instead of argv so prompt bodies do not appear in process listings.
842
+ - Agent prompts are sent through child stdin instead of argv where the provider supports stdin. Codewith durable background agents currently accept prompts as native `agent start` arguments, so OpenLoops stores only bounded status/event evidence and omits raw Codewith event payloads from workflow stdout.
875
843
  - When `--account` or a step `account` is set, OpenLoops resolves `accounts env <profile> --tool <tool>` before spawning the target, strips inherited tool home/API-key variables, and applies the selected profile only to that process. Missing account profiles fail before the provider binary receives the prompt.
876
- - `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>` before `exec`; they do not call OpenAccounts.
844
+ - `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>` before `agent start/read/logs`; they do not call OpenAccounts.
877
845
  - `--sandbox` maps to provider-native sandbox flags. Codewith/Codex accept `read-only`, `workspace-write`, or `danger-full-access`; Cursor accepts `enabled` or `disabled`.
878
846
  - `--permission-mode` maps `plan`, `auto`, and `bypass` where the provider supports it. Claude uses native permission modes, Cursor maps bypass to `--force`, and OpenCode/AICopilot map bypass to `--dangerously-skip-permissions`.
879
847
  - `--variant` is provider-specific reasoning/model effort. Claude maps it to `--effort`, Codewith/Codex map it to `model_reasoning_effort`, and OpenCode/AICopilot pass `--variant`.
880
848
  - Daemon and scheduled runs prepend common user executable directories such as `~/.local/bin` and `~/.bun/bin` before resolving provider CLIs.
881
849
 
882
- For production loops that can mutate repos, prefer disposable worktrees and explicit prompts that name allowed write scope.
850
+ For production loops that can mutate repos, use disposable worktrees (`--worktree-mode required` / `worktreeMode=required`) and explicit prompts that name allowed write scope. Worktrees are executor-enforced: when a target carries worktree metadata, the executor prepares and enters the git worktree before spawning the child process, records the worktree it entered, and with `mode=required` fails the run closed instead of falling back to the original checkout when preparation fails. Remote machine runs with a required worktree are verified fail-closed on the remote side before the target executes.