@hasna/loops 0.4.27 → 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.
- package/CHANGELOG.md +205 -1
- package/README.md +232 -49
- package/dist/api/index.d.ts +20 -19
- package/dist/api/index.js +7770 -1342
- package/dist/cli/index.js +2976 -966
- package/dist/cli/safe-error-context.d.ts +1 -0
- package/dist/daemon/daemon.d.ts +1 -0
- package/dist/daemon/index.js +1899 -499
- package/dist/generated/storage-kit/index.d.ts +1 -1
- package/dist/generated/storage-kit/mode.d.ts +6 -12
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5319 -1096
- package/dist/lib/advancement.d.ts +52 -0
- package/dist/lib/agent-adapter.d.ts +20 -2
- package/dist/lib/auth/route-policy.d.ts +14 -0
- package/dist/lib/auth/tenant-auth.d.ts +38 -0
- package/dist/lib/cloud/mode.d.ts +2 -8
- package/dist/lib/cloud/storage.d.ts +1 -2
- package/dist/lib/cloud/transport.d.ts +4 -18
- package/dist/lib/errors.d.ts +43 -1
- package/dist/lib/executor.d.ts +9 -0
- package/dist/lib/format.d.ts +2 -2
- package/dist/lib/goal/runner.d.ts +36 -3
- package/dist/lib/health.d.ts +1 -1
- package/dist/lib/labels.d.ts +4 -0
- package/dist/lib/loop-status.d.ts +4 -0
- package/dist/lib/migration.d.ts +70 -17
- package/dist/lib/mode.d.ts +2 -5
- package/dist/lib/mode.js +28 -37
- package/dist/lib/route/fields.d.ts +8 -0
- package/dist/lib/route/index.d.ts +2 -2
- package/dist/lib/route/throttle.d.ts +7 -0
- package/dist/lib/route/types.d.ts +3 -0
- package/dist/lib/run-completion.d.ts +18 -0
- package/dist/lib/scheduler.d.ts +5 -11
- package/dist/lib/storage/contract.d.ts +15 -1
- package/dist/lib/storage/index.d.ts +1 -1
- package/dist/lib/storage/index.js +4083 -486
- package/dist/lib/storage/pg-executor.d.ts +10 -5
- package/dist/lib/storage/postgres-loop-storage.d.ts +52 -26
- package/dist/lib/storage/postgres-schema.d.ts +8 -0
- package/dist/lib/storage/postgres-schema.js +1326 -1
- package/dist/lib/storage/postgres.d.ts +3 -1
- package/dist/lib/storage/postgres.js +1356 -27
- package/dist/lib/storage/provider-credentials.d.ts +84 -0
- package/dist/lib/storage/shared-database-transfer.d.ts +136 -0
- package/dist/lib/storage/sqlite.d.ts +15 -2
- package/dist/lib/storage/sqlite.js +1379 -217
- package/dist/lib/storage/tenant-backfill-s3.d.ts +53 -0
- package/dist/lib/storage/tenant-backfill.d.ts +40 -0
- package/dist/lib/store/index.d.ts +14 -8
- package/dist/lib/store.d.ts +129 -7
- package/dist/lib/store.js +1352 -218
- package/dist/lib/templates.d.ts +11 -0
- package/dist/lib/workflow-events.d.ts +6 -0
- package/dist/lib/workflow-provenance.d.ts +8 -0
- package/dist/lib/workflow-runner.d.ts +52 -4
- package/dist/mcp/index.js +2074 -657
- package/dist/runner/index.d.ts +20 -2
- package/dist/runner/index.js +2146 -183
- package/dist/sdk/http.d.ts +364 -4
- package/dist/sdk/http.js +379 -1
- package/dist/sdk/index.d.ts +7 -2
- package/dist/sdk/index.js +2341 -742
- package/dist/serve/index.d.ts +14 -0
- package/dist/serve/index.js +13269 -1830
- package/dist/types.d.ts +75 -3
- package/docs/AUTOMATION_RUNTIME_DESIGN.md +32 -32
- package/docs/CUTOVER-RUNBOOK.md +158 -31
- package/docs/DEPLOYMENT_MODES.md +84 -56
- package/docs/RUNTIME_BOUNDARY.md +26 -26
- package/docs/SHARED-DATABASE-TRANSFER.md +95 -0
- package/docs/SHARED_KIT_EXTRACTION_INVENTORY.md +631 -0
- package/docs/TRANSCRIPT_LOOP_PATTERNS.md +3 -3
- package/docs/UNIFIED_PRODUCT_CONTRACT.md +365 -0
- package/docs/USAGE.md +150 -56
- package/docs/workflows/transcript-feedback-to-loops.json +2 -2
- package/package.json +8 -4
- package/dist/lib/storage/pg-runner-claim.d.ts +0 -40
package/docs/USAGE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Loops
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
|
24
|
-
|
|
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 `
|
|
27
|
-
`
|
|
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
|
-
|
|
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
|
|
|
@@ -86,27 +86,30 @@ leases, running runs, leased work items). Inline command env values are not
|
|
|
86
86
|
exported as secrets; bundles with redacted env values require
|
|
87
87
|
`--allow-redacted` and are marked non-importable.
|
|
88
88
|
|
|
89
|
-
Self-hosted sync commands
|
|
90
|
-
id-preserving import
|
|
89
|
+
Self-hosted sync commands compare local definitions with the control-plane API.
|
|
90
|
+
`push` can apply through the id-preserving `/v1/import` endpoint; `migrate` and
|
|
91
|
+
`pull` remain preview/blocked for remote state that lacks a full export surface:
|
|
91
92
|
|
|
92
93
|
```bash
|
|
93
94
|
loops self-hosted migrate --dry-run
|
|
94
95
|
loops self-hosted push --dry-run
|
|
96
|
+
loops self-hosted push --apply --manifest-file ./self-hosted-push.json
|
|
95
97
|
loops self-hosted pull --dry-run
|
|
96
98
|
```
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
Self-hosted push is safe by default: workflows are archived and loops are
|
|
101
|
+
paused with scheduling pointers cleared, including existing same-id rows that
|
|
102
|
+
need re-neutralizing. `--replace` permits broader same-id data updates, but is
|
|
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.
|
|
106
109
|
|
|
107
110
|
## Install
|
|
108
111
|
|
|
109
|
-
**
|
|
112
|
+
**Loops requires the [Bun](https://bun.sh) runtime (`bun >= 1.0`).** The
|
|
110
113
|
installed binaries are `loops`, `loops-daemon`, `loops-api`, `loops-serve`,
|
|
111
114
|
`loops-runner`, and `loops-mcp`; each uses a `#!/usr/bin/env bun` shebang.
|
|
112
115
|
|
|
@@ -140,7 +143,7 @@ The CLI stores state in `~/.hasna/loops` by default. Set `LOOPS_DATA_DIR` to iso
|
|
|
140
143
|
|
|
141
144
|
## MCP Server
|
|
142
145
|
|
|
143
|
-
|
|
146
|
+
Loops ships a stdio MCP server for safe loop and workflow inspection from
|
|
144
147
|
MCP-capable agents:
|
|
145
148
|
|
|
146
149
|
```bash
|
|
@@ -157,6 +160,7 @@ import { createLoopsMcpServer } from "@hasna/loops/mcp";
|
|
|
157
160
|
Available read tools include `loops_list`, `loops_show`, `loops_runs`,
|
|
158
161
|
`loops_doctor`, `loops_workflows_list`, `loops_workflow_read`, and
|
|
159
162
|
`loops_workflow_validate`.
|
|
163
|
+
`loops_list` and `loops_runs` accept label arrays with AND semantics.
|
|
160
164
|
Resources are available at `loops://runtime` and `loops://tools`.
|
|
161
165
|
Those tools use the same `Store`, public redaction helpers, and workflow parser
|
|
162
166
|
as the CLI and SDK, so read output and validation behavior stay aligned across
|
|
@@ -166,12 +170,16 @@ Mutation tools are disabled by default. Start the server with
|
|
|
166
170
|
`LOOPS_MCP_ALLOW_MUTATIONS=true` only for a trusted local MCP host that should be
|
|
167
171
|
allowed to change loop state. The guarded mutation tools use canonical names:
|
|
168
172
|
`loops_pause`, `loops_resume`, `loops_stop`, `loops_run_now`, `loops_archive`,
|
|
169
|
-
`loops_unarchive`, `loops_create_command`, and
|
|
173
|
+
`loops_unarchive`, `loops_labels_update`, `loops_create_command`, and
|
|
174
|
+
`loops_create_workflow`.
|
|
170
175
|
Deprecated `loop_*` aliases are still registered where compatibility needs them,
|
|
171
176
|
but callers should use the `loops_*` names. Mutation tools do not require or
|
|
172
177
|
accept confirmation-string parameters; the server-side environment opt-in is the
|
|
173
178
|
gate. MCP `loops_run_now` schedules the loop for immediate daemon pickup; inline
|
|
174
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.
|
|
175
183
|
|
|
176
184
|
Keep host-affecting or long-running operations on the CLI: daemon
|
|
177
185
|
start/stop/install/logs, inline `run-now`, `tick`, loop deletion, workflow
|
|
@@ -205,6 +213,16 @@ Codewith auth profile, or workflow step dependency fails without creating a
|
|
|
205
213
|
scheduled loop. Use `--json` with `--preflight` to capture stable machine-readable
|
|
206
214
|
preflight evidence.
|
|
207
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
|
+
|
|
208
226
|
For shell command loops, preflight can only verify the shell plus configured
|
|
209
227
|
accounts because the command string is interpreted later by the shell. Use
|
|
210
228
|
`--no-shell` or workflow command `args` when you need executable-level
|
|
@@ -278,8 +296,34 @@ loops create agent supply-chain-watch \
|
|
|
278
296
|
--prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
|
|
279
297
|
```
|
|
280
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
|
+
|
|
281
325
|
Run an OpenCode loop with an explicit provider/model. OpenCode reads
|
|
282
|
-
`~/.config/opencode/config.json` when no model is supplied, so
|
|
326
|
+
`~/.config/opencode/config.json` when no model is supplied, so Loops rejects
|
|
283
327
|
OpenCode agent targets without `--model` instead of inheriting a stale or
|
|
284
328
|
machine-specific default.
|
|
285
329
|
|
|
@@ -292,7 +336,7 @@ loops create agent opencode-smoke \
|
|
|
292
336
|
--prompt "Reply with exactly OK."
|
|
293
337
|
```
|
|
294
338
|
|
|
295
|
-
Agent loops can also carry advisory
|
|
339
|
+
Agent loops can also carry an auditable advisory session contract:
|
|
296
340
|
|
|
297
341
|
```bash
|
|
298
342
|
loops create agent repo-check \
|
|
@@ -301,13 +345,25 @@ loops create agent repo-check \
|
|
|
301
345
|
--cwd /path/to/repo \
|
|
302
346
|
--prompt "Check the repo and report concrete failures." \
|
|
303
347
|
--allow-tool functions.exec_command \
|
|
304
|
-
--allow-command git,bun
|
|
348
|
+
--allow-command git,bun \
|
|
349
|
+
--safety-reason "isolated repository status inspection"
|
|
305
350
|
```
|
|
306
351
|
|
|
307
|
-
These fields are stored on the loop target
|
|
308
|
-
as `LOOPS_AGENT_ALLOWED_TOOLS`, `LOOPS_AGENT_ALLOWED_COMMANDS`,
|
|
309
|
-
`
|
|
310
|
-
|
|
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.
|
|
311
367
|
|
|
312
368
|
For `codewith` and `aicopilot` account isolation, register matching OpenAccounts tools first if they are not built in on the machine:
|
|
313
369
|
|
|
@@ -354,7 +410,7 @@ from the workflow JSON file's directory:
|
|
|
354
410
|
}
|
|
355
411
|
```
|
|
356
412
|
|
|
357
|
-
|
|
413
|
+
Loops stores the resolved prompt text for execution and records
|
|
358
414
|
`promptSource` metadata with the absolute file path. Public CLI output from
|
|
359
415
|
`show`, `list`, `workflows list`, `workflows show`, `workflows validate`,
|
|
360
416
|
`workflows create`, and `templates render` redacts prompt bodies by default
|
|
@@ -443,8 +499,8 @@ for those. Set `"blockedExitCodes": []` on a gate-named step to opt out.
|
|
|
443
499
|
|
|
444
500
|
## Templates And Task Events
|
|
445
501
|
|
|
446
|
-
|
|
447
|
-
automation domain model. Todos-task routes are
|
|
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
|
|
448
504
|
do not replace the OpenAutomations product queue. See
|
|
449
505
|
[Runtime Boundary](./RUNTIME_BOUNDARY.md) for ownership split and external
|
|
450
506
|
compiler handoff paths (`@hasna/automations` claim-queue,
|
|
@@ -503,7 +559,7 @@ loops templates render routing-remediation \
|
|
|
503
559
|
--var idempotencyKey=routing-health:repo:shard0
|
|
504
560
|
```
|
|
505
561
|
|
|
506
|
-
Custom reusable workflow templates live under the
|
|
562
|
+
Custom reusable workflow templates live under the Loops app data directory:
|
|
507
563
|
`~/.hasna/loops/templates` by default, or `$LOOPS_DATA_DIR/templates` when
|
|
508
564
|
`LOOPS_DATA_DIR` is set. Store templates as declarative JSON files; listing,
|
|
509
565
|
showing, and rendering templates never executes workflow steps or mutates the
|
|
@@ -609,6 +665,8 @@ arguments, and implicit Codewith/Codex full-access defaults. If a custom
|
|
|
609
665
|
Codewith/Codex template uses `permissionMode: "bypass"`, it must also set
|
|
610
666
|
`sandbox` to `workspace-write` or `read-only`. Use built-in templates with
|
|
611
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.
|
|
612
670
|
|
|
613
671
|
Repo-mutating task/event routes should set `worktreeMode=required` so the
|
|
614
672
|
workflow fails fast instead of falling back to the main checkout. When
|
|
@@ -623,7 +681,7 @@ inspection expose the exact checkout.
|
|
|
623
681
|
Before a worker starts, worktree preparation verifies that any existing
|
|
624
682
|
managed path is a real git worktree with the same top-level checkout and the
|
|
625
683
|
same git common directory as the source repo. If the checkout is on a detached
|
|
626
|
-
HEAD or unexpected branch,
|
|
684
|
+
HEAD or unexpected branch, Loops only reattaches it to the expected
|
|
627
685
|
generated branch when the worktree is clean. Dirty worktrees, symlinked paths,
|
|
628
686
|
non-worktree paths, different common dirs, and unrecoverable branch switches
|
|
629
687
|
fail closed with cleanup evidence (`worktreeMode=required`) instead of
|
|
@@ -744,13 +802,26 @@ cat task-created-event.json | loops routes create todos-task \
|
|
|
744
802
|
--max-active 12
|
|
745
803
|
```
|
|
746
804
|
|
|
747
|
-
The limits count active admitted/running
|
|
805
|
+
The limits count active admitted/running Loops work items once per workflow.
|
|
748
806
|
`--max-active-per-project` gates new work for the same project path,
|
|
749
807
|
`--max-active-per-project-group` shares a pool across related projects such as
|
|
750
808
|
`oss`, and `--max-active` is the global routed-workflow cap. Project matching
|
|
751
809
|
uses the canonical git top-level path when available, so repo subdirectories
|
|
752
|
-
share the same project cap. A throttled event records a deferred
|
|
810
|
+
share the same project cap. A throttled event records a deferred Loops
|
|
753
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
|
+
|
|
754
825
|
Re-delivering the event later is safe because handlers dedupe by the work-item
|
|
755
826
|
idempotency key before rendering worktree plans or checking route limits. In
|
|
756
827
|
dry-run mode, throttle counts are not evaluated because opening the live loop
|
|
@@ -812,7 +883,7 @@ The `pilot` policy uses `sandbox=danger-full-access` and is treated as a paused
|
|
|
812
883
|
manual break-glass lane; applying it requires the operator to pass
|
|
813
884
|
`--manual-break-glass` explicitly.
|
|
814
885
|
|
|
815
|
-
When a workflow run starts from an admitted work item,
|
|
886
|
+
When a workflow run starts from an admitted work item, Loops writes a
|
|
816
887
|
manifest under:
|
|
817
888
|
|
|
818
889
|
```text
|
|
@@ -860,9 +931,9 @@ every candidate, so a drain can safely run every few minutes as a deterministic
|
|
|
860
931
|
command loop: it fills only available capacity, writes compact JSON evidence
|
|
861
932
|
when requested, and leaves excess ready tasks in todos for a later drain pass.
|
|
862
933
|
Use `--dry-run` to preview candidates and rendered workflows without mutating
|
|
863
|
-
|
|
934
|
+
Loops state.
|
|
864
935
|
|
|
865
|
-
The route throttle flags count
|
|
936
|
+
The route throttle flags count Loops routed workflow work items, not the
|
|
866
937
|
live background-agent slots inside a provider. For Codewith drains, add
|
|
867
938
|
`--provider-active-cap <n>` (or `--codewith-active-cap <n>`) so each candidate
|
|
868
939
|
checks `codewith agent diagnostics --json` before workflow-loop creation and
|
|
@@ -902,7 +973,7 @@ in with the `auto:route` tag, `route_enabled=true`, or
|
|
|
902
973
|
`automation.allowed=true` should be routed. Keep repo-mutating worker/verifier
|
|
903
974
|
runs on a Codewith account pool with `--worktree-mode required`. Do not dispatch
|
|
904
975
|
or paste task prompts into tmux panes. Use max-active throttles and
|
|
905
|
-
`--max-dispatch` to bound
|
|
976
|
+
`--max-dispatch` to bound Loops agent fan-out, and use
|
|
906
977
|
`--provider-active-cap` plus `--provider-admission-check` to bound live Codewith
|
|
907
978
|
background-agent admission. Write compact evidence into a bounded reports
|
|
908
979
|
directory so operators can audit each drain without unbounded stdout or loop
|
|
@@ -912,7 +983,7 @@ correctly do no work.
|
|
|
912
983
|
|
|
913
984
|
Generated task/event route workflow specs are lifecycle-managed. After a
|
|
914
985
|
generated one-shot route workflow run reaches `succeeded`, `failed`,
|
|
915
|
-
`timed_out`, or `cancelled`,
|
|
986
|
+
`timed_out`, or `cancelled`, Loops archives the generated workflow spec while
|
|
916
987
|
preserving workflow run, step, event, manifest, loop, invocation, and work-item
|
|
917
988
|
history.
|
|
918
989
|
|
|
@@ -930,19 +1001,19 @@ cat event.json | loops routes create generic \
|
|
|
930
1001
|
```
|
|
931
1002
|
|
|
932
1003
|
This is the intended deterministic-to-agentic path: a producer creates a todos
|
|
933
|
-
task, `@hasna/events` delivers `task.created`,
|
|
934
|
-
and admission item,
|
|
1004
|
+
task, `@hasna/events` delivers `task.created`, Loops records the invocation
|
|
1005
|
+
and admission item, Loops creates a worker/verifier workflow when admitted,
|
|
935
1006
|
and the workflow updates todos with evidence. Use account pools so worker and
|
|
936
|
-
verifier steps do not burn the same profile;
|
|
1007
|
+
verifier steps do not burn the same profile; Loops picks deterministically
|
|
937
1008
|
and uses a different verifier profile when the pool has at least two entries.
|
|
938
1009
|
Use `--dry-run` to inspect the rendered invocation, work item, workflow, and
|
|
939
1010
|
loop input without storing anything, including the worktree path and branch for
|
|
940
1011
|
git-backed tasks.
|
|
941
1012
|
|
|
942
1013
|
Generated worker/verifier workflows fail closed when `sandbox=danger-full-access`
|
|
943
|
-
is requested without `manualBreakGlass=true
|
|
944
|
-
unattended task/event routes. Full
|
|
945
|
-
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.
|
|
946
1017
|
|
|
947
1018
|
## Run Receipts
|
|
948
1019
|
|
|
@@ -967,7 +1038,7 @@ mutation-gated `loops_receipt_write`. The SDK exposes `writeReceipt`,
|
|
|
967
1038
|
|
|
968
1039
|
## Transcript-Driven Loops
|
|
969
1040
|
|
|
970
|
-
|
|
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.
|
|
971
1042
|
|
|
972
1043
|
```bash
|
|
973
1044
|
loops workflows validate /path/to/repo/.openloops/transcript-feedback-to-loops.json --preflight
|
|
@@ -1007,7 +1078,7 @@ loops expectations <loop-id-or-name> --json
|
|
|
1007
1078
|
|
|
1008
1079
|
The JSON contains the expectation result, bounded error/stdout/stderr evidence,
|
|
1009
1080
|
a stable failure fingerprint, route metadata, and recommended task fields.
|
|
1010
|
-
|
|
1081
|
+
Loops does not mutate Todos from `health`, `expectations`, or read-only
|
|
1011
1082
|
`health scan`. To turn failed expectations or scan findings into deduped tasks,
|
|
1012
1083
|
use an explicit mutating command:
|
|
1013
1084
|
|
|
@@ -1056,7 +1127,7 @@ Failure classifications are: `rate_limit`, `auth`, `model_not_found`,
|
|
|
1056
1127
|
|
|
1057
1128
|
## Hygiene
|
|
1058
1129
|
|
|
1059
|
-
|
|
1130
|
+
Loops includes deterministic hygiene checks for replacing local maintenance
|
|
1060
1131
|
scripts with package commands:
|
|
1061
1132
|
|
|
1062
1133
|
```bash
|
|
@@ -1110,7 +1181,7 @@ Archived loops are hidden from the default `loops list`, excluded from daemon sc
|
|
|
1110
1181
|
|
|
1111
1182
|
`loops run-now` reports the manual run source:
|
|
1112
1183
|
|
|
1113
|
-
- `source=ad_hoc`: the loop was not due yet, so
|
|
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.
|
|
1114
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.
|
|
1115
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.
|
|
1116
1187
|
|
|
@@ -1159,15 +1230,30 @@ On Linux this writes a user systemd service. On macOS it writes a LaunchAgent pl
|
|
|
1159
1230
|
The adapters intentionally use provider command surfaces instead of pretending every agent has one SDK:
|
|
1160
1231
|
|
|
1161
1232
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
1162
|
-
- 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.
|
|
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.
|
|
1163
1234
|
- AI Copilot and OpenCode use `run --format json --pure`. OpenCode requires an explicit provider/model id because ambient OpenCode config is machine-specific.
|
|
1164
|
-
- Cursor is CLI-first for now via the standalone `agent -p` binary.
|
|
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.
|
|
1165
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.
|
|
1166
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.
|
|
1167
|
-
- When `--account` or a step `account` is set,
|
|
1168
|
-
- `--auth-profile` and step `authProfile` are provider-native
|
|
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.
|
|
1169
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`.
|
|
1170
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.
|
|
1171
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`.
|
|
1172
1258
|
- Daemon and scheduled runs prepend common user executable directories such as `~/.local/bin` and `~/.bun/bin` before resolving provider CLIs.
|
|
1173
1259
|
- Agent targets that set neither `timeoutMs` nor `idleTimeoutMs` get a default
|
|
@@ -1180,6 +1266,14 @@ The adapters intentionally use provider command surfaces instead of pretending e
|
|
|
1180
1266
|
`timeoutMs`/`idleTimeoutMs` per target — `"timeoutMs": null` opts a target
|
|
1181
1267
|
out of both the wall-clock default and the idle watchdog.
|
|
1182
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
|
+
|
|
1183
1277
|
For production loops that can mutate repos, prefer the built-in
|
|
1184
1278
|
`worktreeMode=auto`/`required` path and explicit prompts that name allowed write
|
|
1185
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
|
|
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
|
|
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.
|
|
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,6 +113,8 @@
|
|
|
110
113
|
"bun": ">=1.0.0"
|
|
111
114
|
},
|
|
112
115
|
"dependencies": {
|
|
116
|
+
"@aws-sdk/client-secrets-manager": "^3.1083.0",
|
|
117
|
+
"@hasna/contracts": "0.5.2",
|
|
113
118
|
"@hasna/events": "^0.1.9",
|
|
114
119
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
115
120
|
"@openrouter/ai-sdk-provider": "2.9.1",
|
|
@@ -119,8 +124,7 @@
|
|
|
119
124
|
"zod": "4.4.3"
|
|
120
125
|
},
|
|
121
126
|
"optionalDependencies": {
|
|
122
|
-
"@hasna/machines": "0.0.49"
|
|
123
|
-
"@hasna/contracts": "^0.4.2"
|
|
127
|
+
"@hasna/machines": "0.0.49"
|
|
124
128
|
},
|
|
125
129
|
"devDependencies": {
|
|
126
130
|
"@types/bun": "latest",
|
|
@@ -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>;
|