@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.
- package/CHANGELOG.md +84 -1
- package/README.md +226 -49
- package/dist/api/index.d.ts +20 -19
- package/dist/api/index.js +6846 -1087
- package/dist/cli/index.js +2471 -885
- package/dist/cli/safe-error-context.d.ts +1 -0
- package/dist/daemon/daemon.d.ts +1 -0
- package/dist/daemon/index.js +1786 -493
- 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 +4778 -1016
- 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/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 +4 -17
- package/dist/lib/mode.d.ts +2 -5
- package/dist/lib/mode.js +27 -36
- 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 +3864 -457
- package/dist/lib/storage/pg-executor.d.ts +10 -5
- package/dist/lib/storage/postgres-loop-storage.d.ts +50 -24
- package/dist/lib/storage/postgres-schema.d.ts +8 -0
- package/dist/lib/storage/postgres-schema.js +1323 -1
- package/dist/lib/storage/postgres.d.ts +3 -1
- package/dist/lib/storage/postgres.js +1353 -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 +1299 -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 +11 -8
- package/dist/lib/store.d.ts +52 -7
- package/dist/lib/store.js +1266 -217
- 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 +1961 -651
- package/dist/runner/index.d.ts +20 -2
- package/dist/runner/index.js +2113 -177
- package/dist/sdk/http.d.ts +211 -3
- package/dist/sdk/http.js +301 -0
- package/dist/sdk/index.d.ts +7 -2
- package/dist/sdk/index.js +1959 -711
- package/dist/serve/index.d.ts +14 -0
- package/dist/serve/index.js +12323 -1743
- package/dist/types.d.ts +64 -3
- package/docs/AUTOMATION_RUNTIME_DESIGN.md +32 -32
- package/docs/CUTOVER-RUNBOOK.md +158 -31
- package/docs/DEPLOYMENT_MODES.md +78 -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 +143 -52
- package/docs/workflows/transcript-feedback-to-loops.json +2 -2
- package/package.json +7 -3
- 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
|
|
|
@@ -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.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
schema and `api_keys` table for a
|
|
108
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
311
|
-
as `LOOPS_AGENT_ALLOWED_TOOLS`, `LOOPS_AGENT_ALLOWED_COMMANDS`,
|
|
312
|
-
`
|
|
313
|
-
|
|
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
|
-
|
|
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
|
-
|
|
450
|
-
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
934
|
+
Loops state.
|
|
867
935
|
|
|
868
|
-
The route throttle flags count
|
|
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
|
|
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`,
|
|
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`,
|
|
937
|
-
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,
|
|
938
1006
|
and the workflow updates todos with evidence. Use account pools so worker and
|
|
939
|
-
verifier steps do not burn the same profile;
|
|
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
|
|
947
|
-
unattended task/event routes. Full
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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,
|
|
1171
|
-
- `--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.
|
|
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
|
|
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,7 +113,8 @@
|
|
|
110
113
|
"bun": ">=1.0.0"
|
|
111
114
|
},
|
|
112
115
|
"dependencies": {
|
|
113
|
-
"@
|
|
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>;
|