@hasna/loops 0.4.28 → 0.4.29

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 (77) hide show
  1. package/CHANGELOG.md +84 -1
  2. package/README.md +226 -49
  3. package/dist/api/index.d.ts +20 -19
  4. package/dist/api/index.js +6846 -1087
  5. package/dist/cli/index.js +2471 -885
  6. package/dist/cli/safe-error-context.d.ts +1 -0
  7. package/dist/daemon/daemon.d.ts +1 -0
  8. package/dist/daemon/index.js +1786 -493
  9. package/dist/generated/storage-kit/index.d.ts +1 -1
  10. package/dist/generated/storage-kit/mode.d.ts +6 -12
  11. package/dist/index.d.ts +3 -3
  12. package/dist/index.js +4778 -1016
  13. package/dist/lib/advancement.d.ts +52 -0
  14. package/dist/lib/agent-adapter.d.ts +20 -2
  15. package/dist/lib/auth/route-policy.d.ts +14 -0
  16. package/dist/lib/auth/tenant-auth.d.ts +38 -0
  17. package/dist/lib/cloud/mode.d.ts +2 -8
  18. package/dist/lib/cloud/storage.d.ts +1 -2
  19. package/dist/lib/cloud/transport.d.ts +4 -18
  20. package/dist/lib/errors.d.ts +43 -1
  21. package/dist/lib/format.d.ts +2 -2
  22. package/dist/lib/goal/runner.d.ts +36 -3
  23. package/dist/lib/health.d.ts +1 -1
  24. package/dist/lib/labels.d.ts +4 -0
  25. package/dist/lib/loop-status.d.ts +4 -0
  26. package/dist/lib/migration.d.ts +4 -17
  27. package/dist/lib/mode.d.ts +2 -5
  28. package/dist/lib/mode.js +27 -36
  29. package/dist/lib/route/index.d.ts +2 -2
  30. package/dist/lib/route/throttle.d.ts +7 -0
  31. package/dist/lib/route/types.d.ts +3 -0
  32. package/dist/lib/run-completion.d.ts +18 -0
  33. package/dist/lib/scheduler.d.ts +5 -11
  34. package/dist/lib/storage/contract.d.ts +15 -1
  35. package/dist/lib/storage/index.d.ts +1 -1
  36. package/dist/lib/storage/index.js +3864 -457
  37. package/dist/lib/storage/pg-executor.d.ts +10 -5
  38. package/dist/lib/storage/postgres-loop-storage.d.ts +50 -24
  39. package/dist/lib/storage/postgres-schema.d.ts +8 -0
  40. package/dist/lib/storage/postgres-schema.js +1323 -1
  41. package/dist/lib/storage/postgres.d.ts +3 -1
  42. package/dist/lib/storage/postgres.js +1353 -27
  43. package/dist/lib/storage/provider-credentials.d.ts +84 -0
  44. package/dist/lib/storage/shared-database-transfer.d.ts +136 -0
  45. package/dist/lib/storage/sqlite.d.ts +15 -2
  46. package/dist/lib/storage/sqlite.js +1299 -217
  47. package/dist/lib/storage/tenant-backfill-s3.d.ts +53 -0
  48. package/dist/lib/storage/tenant-backfill.d.ts +40 -0
  49. package/dist/lib/store/index.d.ts +11 -8
  50. package/dist/lib/store.d.ts +52 -7
  51. package/dist/lib/store.js +1266 -217
  52. package/dist/lib/templates.d.ts +11 -0
  53. package/dist/lib/workflow-events.d.ts +6 -0
  54. package/dist/lib/workflow-provenance.d.ts +8 -0
  55. package/dist/lib/workflow-runner.d.ts +52 -4
  56. package/dist/mcp/index.js +1961 -651
  57. package/dist/runner/index.d.ts +20 -2
  58. package/dist/runner/index.js +2113 -177
  59. package/dist/sdk/http.d.ts +211 -3
  60. package/dist/sdk/http.js +301 -0
  61. package/dist/sdk/index.d.ts +7 -2
  62. package/dist/sdk/index.js +1959 -711
  63. package/dist/serve/index.d.ts +14 -0
  64. package/dist/serve/index.js +12323 -1743
  65. package/dist/types.d.ts +64 -3
  66. package/docs/AUTOMATION_RUNTIME_DESIGN.md +32 -32
  67. package/docs/CUTOVER-RUNBOOK.md +158 -31
  68. package/docs/DEPLOYMENT_MODES.md +78 -56
  69. package/docs/RUNTIME_BOUNDARY.md +26 -26
  70. package/docs/SHARED-DATABASE-TRANSFER.md +95 -0
  71. package/docs/SHARED_KIT_EXTRACTION_INVENTORY.md +631 -0
  72. package/docs/TRANSCRIPT_LOOP_PATTERNS.md +3 -3
  73. package/docs/UNIFIED_PRODUCT_CONTRACT.md +365 -0
  74. package/docs/USAGE.md +143 -52
  75. package/docs/workflows/transcript-feedback-to-loops.json +2 -2
  76. package/package.json +7 -3
  77. package/dist/lib/storage/pg-runner-claim.d.ts +0 -40
package/docs/USAGE.md CHANGED
@@ -1,6 +1,6 @@
1
- # OpenLoops
1
+ # Loops
2
2
 
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.
3
+ Loops 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
5
  It supports deterministic command loops, JSON-defined workflows, and guarded CLI adapters for headless coding agents:
6
6
 
@@ -13,18 +13,19 @@ It supports deterministic command loops, JSON-defined workflows, and guarded CLI
13
13
 
14
14
  ## Deployment Modes
15
15
 
16
- OpenLoops defaults to `local`, where SQLite in `LOOPS_DATA_DIR` is
16
+ Loops defaults to `local`, where SQLite in `LOOPS_DATA_DIR` is
17
17
  authoritative and `loops-daemon` executes scheduled work. The package also
18
18
  defines `self_hosted` and `cloud` contracts for non-local control planes:
19
19
 
20
20
  - `self_hosted`: the Hasna-owned AWS/RDS control-plane deployment, served by
21
21
  `loops-serve` and backed by Postgres, with the embeddable `loops-api` contract
22
22
  shared by serve, SDK, and tests. This release exposes storage-backed `/v1`
23
- loop CRUD and run listing, runner registration/claim/heartbeat/finalize
24
- foundations, and local migration previews.
23
+ loop CRUD and run listing, runner claim/lease heartbeat/finalize foundations,
24
+ and local migration previews. Durable runner registration is not exposed
25
+ until the machine-record lifecycle is implemented.
25
26
  - `cloud`: hosted control-plane contract; this release exposes client/runner
26
- status only, and requires `LOOPS_CLOUD_API_URL` plus `LOOPS_CLOUD_TOKEN` or
27
- `HASNA_LOOPS_CLOUD_TOKEN` before status can report ready.
27
+ status only, and requires `HASNA_LOOPS_API_URL` plus
28
+ `HASNA_LOOPS_API_KEY` before status can report ready.
28
29
 
29
30
  Scheduler state is explicit in status JSON. `schedulerState.localStore` is
30
31
  SQLite plus local run artifact files: authoritative in `local`, cache/spool in
@@ -48,12 +49,11 @@ loops self-hosted status
48
49
  loops self-hosted migrate --dry-run
49
50
  loops self-hosted push --dry-run
50
51
  loops self-hosted pull --dry-run
51
- loops self-hosted runner-register --runner-id <id> --machine-id <machine>
52
- loops self-hosted runner-register --runner-id <id> --machine-id <machine> --apply
53
52
  loops cloud status
54
53
  loops-api status
55
54
  loops-serve version
56
- HASNA_LOOPS_DATABASE_URL=... loops-serve migrate --dry-run
55
+ HASNA_LOOPS_MIGRATOR_DATABASE_URL=... loops-serve migrate --dry-run
56
+ HASNA_LOOPS_DATABASE_URL=... HASNA_LOOPS_AUTH_DATABASE_URL=... loops-serve serve
57
57
  loops-runner status
58
58
  ```
59
59
 
@@ -100,16 +100,16 @@ loops self-hosted pull --dry-run
100
100
  Self-hosted push is safe by default: workflows are archived and loops are
101
101
  paused with scheduling pointers cleared, including existing same-id rows that
102
102
  need re-neutralizing. `--replace` permits broader same-id data updates, but is
103
- not required for that safety normalization. Use `loops self-hosted
104
- runner-register` to verify runner registration against an API, then use
105
- `loops-runner run-once` for the current bounded non-workflow
106
- claim/execute/finalize protocol. `loops-serve migrate` applies the Postgres
107
- schema and `api_keys` table for a self-hosted control-plane host.
108
- Runner registration is preview-only unless `--apply` is present.
103
+ not required for that safety normalization. `loops-runner run-once` uses the
104
+ current bounded non-workflow
105
+ claim/execute/finalize protocol. Durable runner registration is intentionally
106
+ absent until the control plane persists and verifies machine records.
107
+ `loops-serve migrate` applies the Postgres schema and `api_keys` table for a
108
+ self-hosted control-plane host.
109
109
 
110
110
  ## Install
111
111
 
112
- **OpenLoops requires the [Bun](https://bun.sh) runtime (`bun >= 1.0`).** The
112
+ **Loops requires the [Bun](https://bun.sh) runtime (`bun >= 1.0`).** The
113
113
  installed binaries are `loops`, `loops-daemon`, `loops-api`, `loops-serve`,
114
114
  `loops-runner`, and `loops-mcp`; each uses a `#!/usr/bin/env bun` shebang.
115
115
 
@@ -143,7 +143,7 @@ The CLI stores state in `~/.hasna/loops` by default. Set `LOOPS_DATA_DIR` to iso
143
143
 
144
144
  ## MCP Server
145
145
 
146
- OpenLoops ships a stdio MCP server for safe loop and workflow inspection from
146
+ Loops ships a stdio MCP server for safe loop and workflow inspection from
147
147
  MCP-capable agents:
148
148
 
149
149
  ```bash
@@ -160,6 +160,7 @@ import { createLoopsMcpServer } from "@hasna/loops/mcp";
160
160
  Available read tools include `loops_list`, `loops_show`, `loops_runs`,
161
161
  `loops_doctor`, `loops_workflows_list`, `loops_workflow_read`, and
162
162
  `loops_workflow_validate`.
163
+ `loops_list` and `loops_runs` accept label arrays with AND semantics.
163
164
  Resources are available at `loops://runtime` and `loops://tools`.
164
165
  Those tools use the same `Store`, public redaction helpers, and workflow parser
165
166
  as the CLI and SDK, so read output and validation behavior stay aligned across
@@ -169,12 +170,16 @@ Mutation tools are disabled by default. Start the server with
169
170
  `LOOPS_MCP_ALLOW_MUTATIONS=true` only for a trusted local MCP host that should be
170
171
  allowed to change loop state. The guarded mutation tools use canonical names:
171
172
  `loops_pause`, `loops_resume`, `loops_stop`, `loops_run_now`, `loops_archive`,
172
- `loops_unarchive`, `loops_create_command`, and `loops_create_workflow`.
173
+ `loops_unarchive`, `loops_labels_update`, `loops_create_command`, and
174
+ `loops_create_workflow`.
173
175
  Deprecated `loop_*` aliases are still registered where compatibility needs them,
174
176
  but callers should use the `loops_*` names. Mutation tools do not require or
175
177
  accept confirmation-string parameters; the server-side environment opt-in is the
176
178
  gate. MCP `loops_run_now` schedules the loop for immediate daemon pickup; inline
177
179
  execution remains CLI-only.
180
+ When `showOutput` is enabled, `loops_runs` caps each stdout/stderr field at
181
+ 32,000 characters, caps output-bearing pages at 25 runs, and caps the aggregate
182
+ MCP JSON response at 128,000 characters.
178
183
 
179
184
  Keep host-affecting or long-running operations on the CLI: daemon
180
185
  start/stop/install/logs, inline `run-now`, `tick`, loop deletion, workflow
@@ -208,6 +213,16 @@ Codewith auth profile, or workflow step dependency fails without creating a
208
213
  scheduled loop. Use `--json` with `--preflight` to capture stable machine-readable
209
214
  preflight evidence.
210
215
 
216
+ For Codewith `--auth-profile` and workflow `authProfile`, local and remote
217
+ preflight first request `codewith profile list --json` and match the requested
218
+ name exactly against the root `data` or `profiles` inventory. That inventory is
219
+ authoritative: `usable: false` rejects a profile, legacy entries without
220
+ `usable` remain usable, and `currentProfile` does not add inventory membership.
221
+ Loops falls back to the human-readable table, including active `*` rows,
222
+ only when JSON mode is unsupported or its inventory is structurally
223
+ unavailable. Embedded NUL bytes, missing profiles, and non-fallback
224
+ profile-list failures fail closed.
225
+
211
226
  For shell command loops, preflight can only verify the shell plus configured
212
227
  accounts because the command string is interpreted later by the shell. Use
213
228
  `--no-shell` or workflow command `args` when you need executable-level
@@ -281,8 +296,34 @@ loops create agent supply-chain-watch \
281
296
  --prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
282
297
  ```
283
298
 
299
+ Codewith `--auth-profile` is provider-native, not an OpenAccounts selector.
300
+ Use `--account` only when you want OpenAccounts environment isolation.
301
+
302
+ ## Labels
303
+
304
+ Command, agent, and workflow loops accept repeatable labels:
305
+
306
+ ```bash
307
+ loops create command repo-status --every 1m --cmd "git status --short" \
308
+ --label BrowserPlan --label maintenance
309
+ loops list --label browserplan --label maintenance
310
+ loops runs --label browserplan
311
+ ```
312
+
313
+ Labels are normalized to lowercase, deduplicated, limited to 32 per loop, and
314
+ must match `[a-z0-9][a-z0-9._-]{0,63}` after normalization. Repeated filters use
315
+ AND semantics. Run filtering uses the loop's current labels rather than a
316
+ historical snapshot on each run.
317
+
318
+ ```bash
319
+ loops labels add repo-status urgent
320
+ loops labels remove repo-status maintenance
321
+ loops labels set repo-status browserplan nightly
322
+ loops labels clear repo-status
323
+ ```
324
+
284
325
  Run an OpenCode loop with an explicit provider/model. OpenCode reads
285
- `~/.config/opencode/config.json` when no model is supplied, so OpenLoops rejects
326
+ `~/.config/opencode/config.json` when no model is supplied, so Loops rejects
286
327
  OpenCode agent targets without `--model` instead of inheriting a stale or
287
328
  machine-specific default.
288
329
 
@@ -295,7 +336,7 @@ loops create agent opencode-smoke \
295
336
  --prompt "Reply with exactly OK."
296
337
  ```
297
338
 
298
- Agent loops can also carry advisory per-session allowlist metadata:
339
+ Agent loops can also carry an auditable advisory session contract:
299
340
 
300
341
  ```bash
301
342
  loops create agent repo-check \
@@ -304,13 +345,25 @@ loops create agent repo-check \
304
345
  --cwd /path/to/repo \
305
346
  --prompt "Check the repo and report concrete failures." \
306
347
  --allow-tool functions.exec_command \
307
- --allow-command git,bun
348
+ --allow-command git,bun \
349
+ --safety-reason "isolated repository status inspection"
308
350
  ```
309
351
 
310
- These fields are stored on the loop target and exposed to the run environment
311
- as `LOOPS_AGENT_ALLOWED_TOOLS`, `LOOPS_AGENT_ALLOWED_COMMANDS`, and
312
- `LOOPS_AGENT_ALLOWLIST_ENFORCEMENT=metadata_only`. They are not enforced by
313
- OpenLoops yet; provider-native enforcement will be added separately.
352
+ These fields are stored on the loop target, appended to provider stdin, and
353
+ exposed as `LOOPS_AGENT_ALLOWED_TOOLS`, `LOOPS_AGENT_ALLOWED_COMMANDS`,
354
+ `LOOPS_AGENT_ALLOWLIST_SAFETY_REASON`,
355
+ `LOOPS_AGENT_ALLOWLIST_ENFORCEMENT=metadata_only`, and
356
+ `LOOPS_AGENT_SESSION_CONTRACT`. A direct agent loop has no workflow run and
357
+ does not create a workflow event. Agent steps inside workflow runs that carry
358
+ an audit contract additionally persist one server-derived
359
+ `agent_session_contract` event per step and run.
360
+ Tool/command restrictions remain advisory metadata: current provider adapters
361
+ do not claim native enforcement.
362
+
363
+ Codewith/Codex `danger-full-access`, Cursor `sandbox=disabled`, and provider
364
+ bypass modes (Claude, Cursor, AI Copilot, and OpenCode) require both a non-empty
365
+ `--safety-reason` and explicit `--manual-break-glass`. A reason alone never
366
+ substitutes for operator break-glass approval.
314
367
 
315
368
  For `codewith` and `aicopilot` account isolation, register matching OpenAccounts tools first if they are not built in on the machine:
316
369
 
@@ -357,7 +410,7 @@ from the workflow JSON file's directory:
357
410
  }
358
411
  ```
359
412
 
360
- OpenLoops stores the resolved prompt text for execution and records
413
+ Loops stores the resolved prompt text for execution and records
361
414
  `promptSource` metadata with the absolute file path. Public CLI output from
362
415
  `show`, `list`, `workflows list`, `workflows show`, `workflows validate`,
363
416
  `workflows create`, and `templates render` redacts prompt bodies by default
@@ -446,8 +499,8 @@ for those. Set `"blockedExitCodes": []` on a gate-named step to opt out.
446
499
 
447
500
  ## Templates And Task Events
448
501
 
449
- OpenLoops is the runtime/scheduler/workflow engine for these flows — not the
450
- automation domain model. Todos-task routes are OpenLoops-native admission; they
502
+ Loops is the runtime/scheduler/workflow engine for these flows — not the
503
+ automation domain model. Todos-task routes are Loops-native admission; they
451
504
  do not replace the OpenAutomations product queue. See
452
505
  [Runtime Boundary](./RUNTIME_BOUNDARY.md) for ownership split and external
453
506
  compiler handoff paths (`@hasna/automations` claim-queue,
@@ -506,7 +559,7 @@ loops templates render routing-remediation \
506
559
  --var idempotencyKey=routing-health:repo:shard0
507
560
  ```
508
561
 
509
- Custom reusable workflow templates live under the OpenLoops app data directory:
562
+ Custom reusable workflow templates live under the Loops app data directory:
510
563
  `~/.hasna/loops/templates` by default, or `$LOOPS_DATA_DIR/templates` when
511
564
  `LOOPS_DATA_DIR` is set. Store templates as declarative JSON files; listing,
512
565
  showing, and rendering templates never executes workflow steps or mutates the
@@ -612,6 +665,8 @@ arguments, and implicit Codewith/Codex full-access defaults. If a custom
612
665
  Codewith/Codex template uses `permissionMode: "bypass"`, it must also set
613
666
  `sandbox` to `workspace-write` or `read-only`. Use built-in templates with
614
667
  explicit break-glass handling for emergency workflows that need full access.
668
+ Claude, Cursor, AI Copilot, and OpenCode bypass modes always require explicit
669
+ break-glass plus a non-empty safety reason.
615
670
 
616
671
  Repo-mutating task/event routes should set `worktreeMode=required` so the
617
672
  workflow fails fast instead of falling back to the main checkout. When
@@ -626,7 +681,7 @@ inspection expose the exact checkout.
626
681
  Before a worker starts, worktree preparation verifies that any existing
627
682
  managed path is a real git worktree with the same top-level checkout and the
628
683
  same git common directory as the source repo. If the checkout is on a detached
629
- HEAD or unexpected branch, OpenLoops only reattaches it to the expected
684
+ HEAD or unexpected branch, Loops only reattaches it to the expected
630
685
  generated branch when the worktree is clean. Dirty worktrees, symlinked paths,
631
686
  non-worktree paths, different common dirs, and unrecoverable branch switches
632
687
  fail closed with cleanup evidence (`worktreeMode=required`) instead of
@@ -747,13 +802,26 @@ cat task-created-event.json | loops routes create todos-task \
747
802
  --max-active 12
748
803
  ```
749
804
 
750
- The limits count active admitted/running OpenLoops work items once per workflow.
805
+ The limits count active admitted/running Loops work items once per workflow.
751
806
  `--max-active-per-project` gates new work for the same project path,
752
807
  `--max-active-per-project-group` shares a pool across related projects such as
753
808
  `oss`, and `--max-active` is the global routed-workflow cap. Project matching
754
809
  uses the canonical git top-level path when available, so repo subdirectories
755
- share the same project cap. A throttled event records a deferred OpenLoops
810
+ share the same project cap. A throttled event records a deferred Loops
756
811
  admission work item with JSON evidence instead of creating another worker loop.
812
+ Route flags or an expanded named policy are authoritative ceilings. Positive
813
+ integer task/event fields (`max_active`, `max_active_per_project`, and
814
+ `max_active_per_project_group`, including camel-case aliases) may only lower an
815
+ already configured ceiling for that admission attempt; metadata cannot create a
816
+ cap, raise it, or override an explicit `--project-group`. The resolved route
817
+ scope, project group, and limits are copied into workflow prompts and invocation
818
+ evidence.
819
+
820
+ Admission caps coordinate only through the active local SQLite store. The count
821
+ and admission write are one transaction, so concurrent routers sharing the same
822
+ `loops.db` serialize correctly. Separate `LOOPS_DATA_DIR` databases or machines
823
+ do not share counts; these flags do not provide distributed coordination.
824
+
757
825
  Re-delivering the event later is safe because handlers dedupe by the work-item
758
826
  idempotency key before rendering worktree plans or checking route limits. In
759
827
  dry-run mode, throttle counts are not evaluated because opening the live loop
@@ -815,7 +883,7 @@ The `pilot` policy uses `sandbox=danger-full-access` and is treated as a paused
815
883
  manual break-glass lane; applying it requires the operator to pass
816
884
  `--manual-break-glass` explicitly.
817
885
 
818
- When a workflow run starts from an admitted work item, OpenLoops writes a
886
+ When a workflow run starts from an admitted work item, Loops writes a
819
887
  manifest under:
820
888
 
821
889
  ```text
@@ -863,9 +931,9 @@ every candidate, so a drain can safely run every few minutes as a deterministic
863
931
  command loop: it fills only available capacity, writes compact JSON evidence
864
932
  when requested, and leaves excess ready tasks in todos for a later drain pass.
865
933
  Use `--dry-run` to preview candidates and rendered workflows without mutating
866
- OpenLoops state.
934
+ Loops state.
867
935
 
868
- The route throttle flags count OpenLoops routed workflow work items, not the
936
+ The route throttle flags count Loops routed workflow work items, not the
869
937
  live background-agent slots inside a provider. For Codewith drains, add
870
938
  `--provider-active-cap <n>` (or `--codewith-active-cap <n>`) so each candidate
871
939
  checks `codewith agent diagnostics --json` before workflow-loop creation and
@@ -905,7 +973,7 @@ in with the `auto:route` tag, `route_enabled=true`, or
905
973
  `automation.allowed=true` should be routed. Keep repo-mutating worker/verifier
906
974
  runs on a Codewith account pool with `--worktree-mode required`. Do not dispatch
907
975
  or paste task prompts into tmux panes. Use max-active throttles and
908
- `--max-dispatch` to bound OpenLoops agent fan-out, and use
976
+ `--max-dispatch` to bound Loops agent fan-out, and use
909
977
  `--provider-active-cap` plus `--provider-admission-check` to bound live Codewith
910
978
  background-agent admission. Write compact evidence into a bounded reports
911
979
  directory so operators can audit each drain without unbounded stdout or loop
@@ -915,7 +983,7 @@ correctly do no work.
915
983
 
916
984
  Generated task/event route workflow specs are lifecycle-managed. After a
917
985
  generated one-shot route workflow run reaches `succeeded`, `failed`,
918
- `timed_out`, or `cancelled`, OpenLoops archives the generated workflow spec while
986
+ `timed_out`, or `cancelled`, Loops archives the generated workflow spec while
919
987
  preserving workflow run, step, event, manifest, loop, invocation, and work-item
920
988
  history.
921
989
 
@@ -933,19 +1001,19 @@ cat event.json | loops routes create generic \
933
1001
  ```
934
1002
 
935
1003
  This is the intended deterministic-to-agentic path: a producer creates a todos
936
- task, `@hasna/events` delivers `task.created`, OpenLoops records the invocation
937
- and admission item, OpenLoops creates a worker/verifier workflow when admitted,
1004
+ task, `@hasna/events` delivers `task.created`, Loops records the invocation
1005
+ and admission item, Loops creates a worker/verifier workflow when admitted,
938
1006
  and the workflow updates todos with evidence. Use account pools so worker and
939
- verifier steps do not burn the same profile; OpenLoops picks deterministically
1007
+ verifier steps do not burn the same profile; Loops picks deterministically
940
1008
  and uses a different verifier profile when the pool has at least two entries.
941
1009
  Use `--dry-run` to inspect the rendered invocation, work item, workflow, and
942
1010
  loop input without storing anything, including the worktree path and branch for
943
1011
  git-backed tasks.
944
1012
 
945
1013
  Generated worker/verifier workflows fail closed when `sandbox=danger-full-access`
946
- is requested without `manualBreakGlass=true`. Use `workspace-write` for
947
- unattended task/event routes. Full access is an explicit manual emergency path,
948
- not a default automation mode.
1014
+ is requested without both `manualBreakGlass=true` and a non-empty
1015
+ `safetyReason`. Use `workspace-write` for unattended task/event routes. Full
1016
+ access is an explicit manual emergency path, not a default automation mode.
949
1017
 
950
1018
  ## Run Receipts
951
1019
 
@@ -970,7 +1038,7 @@ mutation-gated `loops_receipt_write`. The SDK exposes `writeReceipt`,
970
1038
 
971
1039
  ## Transcript-Driven Loops
972
1040
 
973
- OpenLoops can turn long-form media or meeting transcripts into recurring workflow work when paired with `iapp-transcriber`. The template at `docs/workflows/transcript-feedback-to-loops.json` transcribes an authorized media URL, asks an agent to extract recurring loop candidates, authors workflow specs, and validates generated workflows before scheduling. Copy it into the target repo, replace `/path/to/repo` with that repo's absolute path, and provide `TRANSCRIBER_SOURCE_URL` through the runner environment or a private, uncommitted workflow copy before storing or scheduling it. Do not commit private or signed media URLs.
1041
+ Loops can turn long-form media or meeting transcripts into recurring workflow work when paired with `iapp-transcriber`. The template at `docs/workflows/transcript-feedback-to-loops.json` transcribes an authorized media URL, asks an agent to extract recurring loop candidates, authors workflow specs, and validates generated workflows before scheduling. Copy it into the target repo, replace `/path/to/repo` with that repo's absolute path, and provide `TRANSCRIBER_SOURCE_URL` through the runner environment or a private, uncommitted workflow copy before storing or scheduling it. Do not commit private or signed media URLs.
974
1042
 
975
1043
  ```bash
976
1044
  loops workflows validate /path/to/repo/.openloops/transcript-feedback-to-loops.json --preflight
@@ -1010,7 +1078,7 @@ loops expectations <loop-id-or-name> --json
1010
1078
 
1011
1079
  The JSON contains the expectation result, bounded error/stdout/stderr evidence,
1012
1080
  a stable failure fingerprint, route metadata, and recommended task fields.
1013
- OpenLoops does not mutate Todos from `health`, `expectations`, or read-only
1081
+ Loops does not mutate Todos from `health`, `expectations`, or read-only
1014
1082
  `health scan`. To turn failed expectations or scan findings into deduped tasks,
1015
1083
  use an explicit mutating command:
1016
1084
 
@@ -1059,7 +1127,7 @@ Failure classifications are: `rate_limit`, `auth`, `model_not_found`,
1059
1127
 
1060
1128
  ## Hygiene
1061
1129
 
1062
- OpenLoops includes deterministic hygiene checks for replacing local maintenance
1130
+ Loops includes deterministic hygiene checks for replacing local maintenance
1063
1131
  scripts with package commands:
1064
1132
 
1065
1133
  ```bash
@@ -1113,7 +1181,7 @@ Archived loops are hidden from the default `loops list`, excluded from daemon sc
1113
1181
 
1114
1182
  `loops run-now` reports the manual run source:
1115
1183
 
1116
- - `source=ad_hoc`: the loop was not due yet, so OpenLoops created a one-off manual slot. This is a single immediate attempt and does not schedule retries or consume the future scheduled slot.
1184
+ - `source=ad_hoc`: the loop was not due yet, so Loops created a one-off manual slot. This is a single immediate attempt and does not schedule retries or consume the future scheduled slot.
1117
1185
  - `source=due_slot`: the persisted scheduled slot was already due, so the manual run claims that slot and advances or retries the loop using normal scheduler rules.
1118
1186
  - `source=retry_slot`: the loop was waiting on a failed slot retry, so the manual run claims that retry slot and advances the loop using normal retry rules.
1119
1187
 
@@ -1162,15 +1230,30 @@ On Linux this writes a user systemd service. On macOS it writes a LaunchAgent pl
1162
1230
  The adapters intentionally use provider command surfaces instead of pretending every agent has one SDK:
1163
1231
 
1164
1232
  - Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
1165
- - Codewith runs non-interactive `codewith --ask-for-approval never exec --json` sessions by default. exec starts a fresh session per invocation, avoiding the multi-megabyte rollout history that `codewith agent start` reloaded every turn (which drove `context_length_exceeded` silent no-ops), and it keeps network egress for gh/git — the `workspace-write` sandbox opts back into `sandbox_workspace_write.network_access`. Codewith exec is remote-capable like codex. OpenLoops rejects Codewith `extraArgs` that try to force `exec`, `--ephemeral`, `--json`, or other exec launch flags that the adapter already manages.
1233
+ - Codewith runs non-interactive `codewith --ask-for-approval never exec --json` sessions by default. exec starts a fresh session per invocation, avoiding the multi-megabyte rollout history that `codewith agent start` reloaded every turn (which drove `context_length_exceeded` silent no-ops), and it keeps network egress for gh/git — the `workspace-write` sandbox opts back into `sandbox_workspace_write.network_access`. Codewith exec is remote-capable like codex.
1166
1234
  - AI Copilot and OpenCode use `run --format json --pure`. OpenCode requires an explicit provider/model id because ambient OpenCode config is machine-specific.
1167
- - 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.
1235
+ - Cursor is CLI-first for now via the standalone `agent -p` binary. Loops no longer falls back to `cursor agent`; install the standalone Cursor Agent CLI so preflight and scheduled runs use the same executable.
1168
1236
  - Codex uses `codex --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
1169
1237
  - Agent prompts are sent through child stdin instead of argv where the provider supports stdin, including Codewith `exec` (which reads instructions from stdin when no positional prompt is given), so the prompt never lands on argv.
1170
- - 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.
1171
- - `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>` on the `exec` invocation; they do not call OpenAccounts.
1238
+ - When `--account` or a step `account` is set, Loops 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.
1239
+ - `--auth-profile` and step `authProfile` are provider-native Codewith selectors passed as `--auth-profile <name>` on the `exec` invocation; they do not call OpenAccounts. Local and remote preflight share the JSON-first, exact-name contract described above, use human-table parsing only as a compatibility fallback, and fail closed for unusable or missing profiles, NUL-containing names, and non-fallback list failures.
1172
1240
  - `--sandbox` maps to provider-native sandbox flags. Codewith/Codex accept `read-only`, `workspace-write`, or `danger-full-access`; Cursor accepts `enabled` or `disabled`.
1241
+ - `--allow-tool` and `--allow-command` declare advisory, metadata-only
1242
+ restrictions and require `--safety-reason`. The exact contract is persisted
1243
+ and emitted for audit, but Loops does not claim provider-side enforcement.
1244
+ Relaxed sandboxes and native provider bypass modes also require explicit
1245
+ `--manual-break-glass`.
1173
1246
  - `--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`.
1247
+ - `extraArgs` is fail-closed: every provider currently rejects non-empty
1248
+ passthrough arguments, including unknown options, positional subcommands,
1249
+ split values, `--option=value`, and attached short-option forms. Configure
1250
+ execution, output, permissions, sandboxing, model, cwd, and other supported
1251
+ behavior through the modeled agent-target fields. A passthrough option must
1252
+ be explicitly reviewed and added to the provider allowlist before use.
1253
+ Legacy persisted targets are not silently accepted or rewritten: execution
1254
+ fails validation until `extraArgs` is removed and replaced with modeled
1255
+ fields. API and migration imports reject those targets instead of stripping
1256
+ the arguments; update the source record and retry the import.
1174
1257
  - `--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`.
1175
1258
  - Daemon and scheduled runs prepend common user executable directories such as `~/.local/bin` and `~/.bun/bin` before resolving provider CLIs.
1176
1259
  - Agent targets that set neither `timeoutMs` nor `idleTimeoutMs` get a default
@@ -1183,6 +1266,14 @@ The adapters intentionally use provider command surfaces instead of pretending e
1183
1266
  `timeoutMs`/`idleTimeoutMs` per target — `"timeoutMs": null` opts a target
1184
1267
  out of both the wall-clock default and the idle watchdog.
1185
1268
 
1269
+ For hosted workflow runs, the control plane derives any required agent session
1270
+ contract from the stored workflow step and persists it before execution. Reusing an
1271
+ older workflow run backfills a missing valid contract idempotently. Stored
1272
+ pre-contract workflows with unsafe/invalid agent options, mismatched stored
1273
+ contracts, duplicate contracts, command-step contracts, and client-fabricated
1274
+ contracts fail closed. Direct agent loops continue to expose their contract
1275
+ through prompt/environment metadata only.
1276
+
1186
1277
  For production loops that can mutate repos, prefer the built-in
1187
1278
  `worktreeMode=auto`/`required` path and explicit prompts that name allowed write
1188
1279
  scope. Use `main` or `off` only for operations that intentionally need the
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "transcript-feedback-to-loops",
3
- "description": "Turn an authorized media transcript into a reviewable backlog of recurring OpenLoops workflows.",
3
+ "description": "Turn an authorized media transcript into a reviewable backlog of recurring Loops workflows.",
4
4
  "version": 1,
5
5
  "steps": [
6
6
  {
@@ -49,7 +49,7 @@
49
49
  "provider": "codewith",
50
50
  "cwd": "/path/to/repo",
51
51
  "timeoutMs": null,
52
- "prompt": "Read .openloops/transcripts/latest-transcript.json and create or update docs/loop-backlog.md. Extract only recurring work that is useful enough to schedule through OpenLoops. For each candidate include cadence, target provider, allowed write scope, expected artifacts, verification command, stop condition, and the specific transcript insight that justifies it. Prefer loops for code review/security, customer feedback triage, maintenance PRs, CI optimization, knowledge capture, and workflow hygiene."
52
+ "prompt": "Read .openloops/transcripts/latest-transcript.json and create or update docs/loop-backlog.md. Extract only recurring work that is useful enough to schedule through Loops. For each candidate include cadence, target provider, allowed write scope, expected artifacts, verification command, stop condition, and the specific transcript insight that justifies it. Prefer loops for code review/security, customer feedback triage, maintenance PRs, CI optimization, knowledge capture, and workflow hygiene."
53
53
  }
54
54
  },
55
55
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/loops",
3
- "version": "0.4.28",
3
+ "version": "0.4.29",
4
4
  "description": "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -79,11 +79,14 @@
79
79
  "build:bin": "bun build src/cli/index.ts --compile --outfile dist/loops",
80
80
  "typecheck": "tsc --noEmit",
81
81
  "test": "bun test",
82
+ "check:branding": "bun test scripts/check-branding.test.mjs && bun run scripts/check-branding.mjs",
82
83
  "test:boundary": "bun run scripts/no-private-cloud-boundary.mjs",
84
+ "check:contracts": "bun run scripts/check-storage-kit.mjs && bun run scripts/check-contract-conformance.mjs",
85
+ "check:supply-chain": "bun audit && bun audit --production && bun run check:contracts && bun run check:branding && bun pm pack --dry-run --ignore-scripts && bun run test:boundary && bun run scripts/check-packed-boundary.mjs",
83
86
  "prepare": "test -d dist || bun run build",
84
87
  "dev:cli": "bun run src/cli/index.ts",
85
88
  "dev:daemon": "bun run src/daemon/index.ts",
86
- "prepublishOnly": "bun run build && bun run typecheck && bun test && bun run test:boundary"
89
+ "prepublishOnly": "bun run build && bun run typecheck && bun test && bun run test:boundary && bun run check:supply-chain"
87
90
  },
88
91
  "keywords": [
89
92
  "loops",
@@ -110,7 +113,8 @@
110
113
  "bun": ">=1.0.0"
111
114
  },
112
115
  "dependencies": {
113
- "@hasna/contracts": "^0.5.1",
116
+ "@aws-sdk/client-secrets-manager": "^3.1083.0",
117
+ "@hasna/contracts": "0.5.2",
114
118
  "@hasna/events": "^0.1.9",
115
119
  "@modelcontextprotocol/sdk": "^1.29.0",
116
120
  "@openrouter/ai-sdk-provider": "2.9.1",
@@ -1,40 +0,0 @@
1
- import type { PoolQueryClient } from "../../generated/storage-kit/query.js";
2
- export interface ClaimedRunRow extends Record<string, unknown> {
3
- id: string;
4
- loop_id: string;
5
- loop_name: string;
6
- scheduled_for: string | Date;
7
- attempt: number;
8
- status: string;
9
- claimed_by: string | null;
10
- claim_token: string | null;
11
- lease_expires_at: string | Date | null;
12
- }
13
- export interface ClaimNextRunOptions {
14
- runnerId: string;
15
- leaseMs: number;
16
- claimToken: string;
17
- /** Statuses a run may be in to be claimable. Defaults to queued + lease-expired running. */
18
- now?: Date;
19
- }
20
- /**
21
- * Atomically claim the next claimable run for `runnerId`.
22
- *
23
- * A run is claimable when it is `queued`, or `running` with an expired lease
24
- * (crash recovery). Returns the claimed row, or `null` when nothing was
25
- * available. Two concurrent callers are guaranteed to receive distinct rows
26
- * (or one receives `null`) thanks to `FOR UPDATE SKIP LOCKED`.
27
- */
28
- export declare function claimNextRun(client: PoolQueryClient, options: ClaimNextRunOptions): Promise<ClaimedRunRow | null>;
29
- /**
30
- * Heartbeat a claimed run's lease. Returns true when the lease was extended
31
- * (the caller still owns the run), false when ownership was lost (claim token
32
- * mismatch, run finalized, or lease already stolen).
33
- */
34
- export declare function heartbeatRunLease(client: PoolQueryClient, input: {
35
- runId: string;
36
- runnerId: string;
37
- claimToken: string;
38
- leaseMs: number;
39
- now?: Date;
40
- }): Promise<boolean>;