@hasna/loops 0.4.0 → 0.4.2

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 CHANGED
@@ -5,6 +5,24 @@ documented in this file. Version entries are generated from the
5
5
  conventional-commit git history; one commit maps to one released patch version
6
6
  unless noted.
7
7
 
8
+ ## 0.4.1 (2026-07-03)
9
+
10
+ Contract-foundation release for the Mailery-style local/self-hosted/cloud
11
+ deployment split.
12
+
13
+ ### Added
14
+
15
+ - Deployment-mode contract docs covering `local`, `self_hosted`, and `cloud`
16
+ authority, cache/spool, API, runner, and hosted-boundary semantics.
17
+ - Public mode resolver and status helpers exported from `@hasna/loops` and the
18
+ `@hasna/loops/mode` subpath.
19
+ - CLI status surfaces: `loops mode`, `loops self-hosted status`, and
20
+ `loops cloud status`.
21
+ - Foundation binaries and package subpaths for `loops-api` / `@hasna/loops/api`
22
+ and `loops-runner` / `@hasna/loops/runner`.
23
+ - Private-hosted-boundary test coverage to keep hosted implementation details
24
+ and obvious credential patterns out of the public package.
25
+
8
26
  ## 0.4.0 (2026-07-02)
9
27
 
10
28
  Audit-hardening release: write-path secret scrubbing, process-group reaping,
package/README.md CHANGED
@@ -5,7 +5,8 @@ OpenLoops is a local CLI and daemon for persistent loops and workflows: schedule
5
5
  Naming: the product is **OpenLoops**, published on npm as
6
6
  [`@hasna/loops`](https://www.npmjs.com/package/@hasna/loops) and developed in the
7
7
  [`hasna/loops`](https://github.com/hasna/loops) repository. The installed
8
- binaries are `loops`, `loops-daemon`, and `loops-mcp`.
8
+ binaries are `loops`, `loops-daemon`, `loops-api`, `loops-runner`, and
9
+ `loops-mcp`.
9
10
 
10
11
  It supports deterministic command loops, JSON-defined workflows, and guarded CLI adapters for headless coding agents:
11
12
 
@@ -16,12 +17,47 @@ It supports deterministic command loops, JSON-defined workflows, and guarded CLI
16
17
  - `opencode run`
17
18
  - `codex exec`
18
19
 
20
+ ## Deployment Modes
21
+
22
+ OpenLoops has three deployment modes:
23
+
24
+ - `local`: SQLite in `LOOPS_DATA_DIR` is authoritative and `loops-daemon` executes scheduled work.
25
+ - `self_hosted`: a user-operated `loops-api` control plane contract. This release exposes status, storage-backed `/v1` loop CRUD and run listing, runner claim/heartbeat/finalize protocol endpoints, and a one-shot `loops-runner run-once` execution path for embedded control-plane hosts; full fleet rollout and migration tooling are follow-up work.
26
+ - `cloud`: a hosted control-plane contract. This release exposes client/runner status only; hosted tenant auth and infrastructure live outside this package.
27
+
28
+ `local` is the default and requires no network, token, Postgres, or hosted
29
+ service. Set `LOOPS_MODE` or `HASNA_LOOPS_MODE` to `local`, `self_hosted`, or
30
+ `cloud` to choose explicitly. Without an explicit mode, `LOOPS_CLOUD_API_URL`
31
+ selects `cloud`, while `LOOPS_API_URL` or `LOOPS_DATABASE_URL` selects
32
+ `self_hosted`.
33
+
34
+ The public `@hasna/loops` package owns the local runtime, mode resolver,
35
+ self-hosted API contract, runner contract, SDK, MCP server, and CLI. Hosted
36
+ tenant auth, account administration, and infrastructure live outside this
37
+ package. Cloud mode is a public contract until a cloud-specific hosted URL and
38
+ cloud token are configured through `LOOPS_CLOUD_API_URL` plus
39
+ `LOOPS_CLOUD_TOKEN` or `HASNA_LOOPS_CLOUD_TOKEN`.
40
+
41
+ Useful status commands:
42
+
43
+ ```bash
44
+ loops mode
45
+ loops --json mode
46
+ loops self-hosted status
47
+ loops cloud status
48
+ loops-api status
49
+ loops-runner status
50
+ ```
51
+
52
+ See [Deployment Modes](docs/DEPLOYMENT_MODES.md) for the full package boundary
53
+ and machine-placement contract.
54
+
19
55
  ## Install
20
56
 
21
57
  **OpenLoops requires the [Bun](https://bun.sh) runtime (`bun >= 1.0`).** The
22
- `loops`, `loops-daemon`, and `loops-mcp` binaries run with a `#!/usr/bin/env bun`
23
- shebang, so Bun must be on `PATH` even when the package is installed with npm.
24
- Node.js is not a supported runtime.
58
+ `loops`, `loops-daemon`, `loops-api`, `loops-runner`, and `loops-mcp` binaries
59
+ run with a `#!/usr/bin/env bun` shebang, so Bun must be on `PATH` even when the
60
+ package is installed with npm. Node.js is not a supported runtime.
25
61
 
26
62
  From npm (with Bun installed):
27
63
 
@@ -67,8 +103,9 @@ The package also exports the server factory for embedded hosts:
67
103
  import { createLoopsMcpServer } from "@hasna/loops/mcp";
68
104
  ```
69
105
 
70
- Available read tools include `loops_list`, `loops_show`, `loop_runs`,
71
- `loops_doctor`, `workflows_list`, `workflow_read`, and `workflow_validate`.
106
+ Available read tools include `loops_list`, `loops_show`, `loops_runs`,
107
+ `loops_doctor`, `loops_workflows_list`, `loops_workflow_read`, and
108
+ `loops_workflow_validate`.
72
109
  Resources are available at `loops://runtime` and `loops://tools`.
73
110
  Those tools use the same `Store`, public redaction helpers, and workflow parser
74
111
  as the CLI and SDK, so read output and validation behavior stay aligned across
@@ -76,15 +113,19 @@ surfaces.
76
113
 
77
114
  Mutation tools are disabled by default. Start the server with
78
115
  `LOOPS_MCP_ALLOW_MUTATIONS=true` only for a trusted local MCP host that should be
79
- allowed to change loop state. Even then, mutation tools require exact
80
- confirmation strings: `loop_pause`, `loop_resume`, `loop_run_now`,
81
- `loop_create_command`, and `loop_create_workflow`. MCP `loop_run_now` schedules
82
- the loop for immediate daemon pickup; inline execution remains CLI-only.
116
+ allowed to change loop state. The guarded mutation tools use canonical names:
117
+ `loops_pause`, `loops_resume`, `loops_stop`, `loops_run_now`, `loops_archive`,
118
+ `loops_unarchive`, `loops_create_command`, and `loops_create_workflow`.
119
+ Deprecated `loop_*` aliases are still registered where compatibility needs them,
120
+ but callers should use the `loops_*` names. Mutation tools do not require or
121
+ accept confirmation-string parameters; the server-side environment opt-in is the
122
+ gate. MCP `loops_run_now` schedules the loop for immediate daemon pickup; inline
123
+ execution remains CLI-only.
83
124
 
84
125
  Keep host-affecting or long-running operations on the CLI: daemon
85
- start/stop/install/logs, inline `run-now`, `tick`, loop removal/archive
86
- maintenance, workflow create/migrate/cancel/recover, agent loop creation,
87
- template materialization, and event-route drains.
126
+ start/stop/install/logs, inline `run-now`, `tick`, loop deletion, workflow
127
+ create/migrate/cancel/recover, agent loop creation, template materialization,
128
+ and event-route drains.
88
129
 
89
130
  ## Create Loops
90
131
 
@@ -236,7 +277,7 @@ Inspect configured and runtime goal state:
236
277
 
237
278
  ```bash
238
279
  loops goal show <loop-or-goal-id>
239
- loops goal status <goal-run-id-or-loop-run-id>
280
+ loops goal show <goal-run-id-or-loop-run-id>
240
281
  ```
241
282
 
242
283
  Workflow JSON can also embed goals at the workflow or step level:
@@ -339,11 +380,11 @@ loops templates render todos-task-worker-verifier \
339
380
  --var taskTitle="Fix parser" \
340
381
  --var projectPath=/path/to/repo \
341
382
  --var provider=codewith \
342
- --var authProfilePool=account004,account005,account006 \
383
+ --var authProfilePool=account001,account002,account003 \
343
384
  --var sandbox=workspace-write \
344
385
  --var todosProjectPath=$HOME/.hasna/loops \
345
386
  --var addDirs=$HOME/.hasna/todos,$HOME/.hasna/loops
346
- loops templates create-workflow todos-task-worker-verifier \
387
+ loops workflows create --template todos-task-worker-verifier \
347
388
  --var taskId=<task-id> \
348
389
  --var projectPath=/path/to/repo
349
390
  loops templates render event-worker-verifier \
@@ -356,7 +397,7 @@ loops templates render bounded-agent-worker-verifier \
356
397
  --var objective="Check docs drift and queue tasks for gaps" \
357
398
  --var projectPath=/path/to/repo \
358
399
  --var provider=codewith \
359
- --var authProfilePool=account004,account005 \
400
+ --var authProfilePool=account001,account002 \
360
401
  --var sandbox=workspace-write \
361
402
  --var worktreeMode=required
362
403
  loops templates render pr-review \
@@ -441,30 +482,31 @@ loops templates show custom-report
441
482
  loops templates render custom-report \
442
483
  --var objective="Check docs drift" \
443
484
  --var projectPath=/path/to/repo
444
- loops templates create-workflow custom-report \
485
+ loops workflows create --template custom-report \
445
486
  --var objective="Check docs drift" \
446
487
  --var projectPath=/path/to/repo
447
488
  ```
448
489
 
449
490
  Use `--source builtin`, `--source custom`, or `--source all` on
450
- `list`, `show`, `render`, and `create-workflow` when automation needs an
451
- explicit source. Custom template ids and names cannot override built-ins.
491
+ `templates list`, `templates show`, `templates render`, and
492
+ `workflows create --template` when automation needs an explicit source. Custom
493
+ template ids and names cannot override built-ins.
452
494
  Custom templates fail closed for `danger-full-access`, dangerous passthrough
453
495
  arguments, and implicit Codewith/Codex full-access defaults. If a custom
454
496
  Codewith/Codex template uses `permissionMode: "bypass"`, it must also set
455
497
  `sandbox` to `workspace-write` or `read-only`. Use built-in templates with
456
498
  explicit break-glass handling for emergency workflows that need full access.
457
499
 
458
- For event-driven task automation, `loops events handle todos-task` reads a
500
+ For event-driven task automation, `loops routes create todos-task` reads a
459
501
  Hasna event envelope from stdin or `HASNA_EVENT_JSON`, records a
460
502
  `WorkflowInvocation`, upserts an admission work item, and admits that work item
461
503
  into a deduped one-shot workflow loop when route capacity allows:
462
504
 
463
505
  ```bash
464
- cat task-created-event.json | loops events handle todos-task \
506
+ cat task-created-event.json | loops routes create todos-task \
465
507
  --template task-lifecycle \
466
508
  --provider codewith \
467
- --auth-profile-pool account004,account005,account006 \
509
+ --auth-profile-pool account001,account002,account003 \
468
510
  --permission-mode bypass \
469
511
  --sandbox workspace-write \
470
512
  --todos-project "$HOME/.hasna/loops" \
@@ -497,7 +539,7 @@ selected.
497
539
  loops routes drain todos-task \
498
540
  --dry-run \
499
541
  --provider-rule area=frontend:claude:claude-ui-a,claude-ui-b \
500
- --provider-rule area=backend:codewith:account004,account005 \
542
+ --provider-rule area=backend:codewith:account001,account002 \
501
543
  --worktree-mode required
502
544
  ```
503
545
 
@@ -534,9 +576,9 @@ For other Hasna apps that expose `@hasna/events` webhooks, use the generic
534
576
  handler:
535
577
 
536
578
  ```bash
537
- cat event.json | loops events handle generic \
579
+ cat event.json | loops routes create generic \
538
580
  --provider codewith \
539
- --auth-profile-pool account004,account005,account006 \
581
+ --auth-profile-pool account001,account002,account003 \
540
582
  --permission-mode bypass \
541
583
  --sandbox workspace-write \
542
584
  --project-path /path/to/repo \
@@ -579,7 +621,7 @@ loops routes requeue <work-item-id> --reason "fixed upstream blocker"
579
621
  loops routes invocations
580
622
  ```
581
623
 
582
- For Hasna OSS task-created routing, use a deterministic drain instead of tmux
624
+ For OSS task-created routing, use a deterministic drain instead of tmux
583
625
  dispatch:
584
626
 
585
627
  ```bash
@@ -587,10 +629,10 @@ loops routes schedule todos-task oss-task-route-drain \
587
629
  --every 5m \
588
630
  --todos-project "$HOME/.hasna/loops" \
589
631
  --template task-lifecycle \
590
- --project-path-prefix /home/hasna/workspace/hasna/opensource \
632
+ --project-path-prefix "$HOME/workspace/example/opensource" \
591
633
  --tags auto:route \
592
634
  --provider codewith \
593
- --auth-profile-pool account004,account005,account006 \
635
+ --auth-profile-pool account001,account002,account003 \
594
636
  --add-dir "$HOME/.hasna/todos,$HOME/.hasna/loops" \
595
637
  --project-group oss \
596
638
  --max-dispatch 2 \
@@ -633,7 +675,7 @@ import { openAutomationsRuntimeBinding } from "@hasna/loops";
633
675
 
634
676
  const binding = openAutomationsRuntimeBinding();
635
677
  console.log(binding.handoff); // "claim-queue"
636
- console.log(binding.eventHandoff.handlerCommand); // "loops events handle generic"
678
+ console.log(binding.eventHandoff.handlerCommand); // "loops routes create generic"
637
679
  ```
638
680
 
639
681
  The claim-queue handoff uses the OpenAutomations CLI or SDK:
@@ -646,18 +688,18 @@ automations queue fail <action-id> --runner open-loops:<worker-id> --code <code>
646
688
 
647
689
  For explicit event workflow routing, OpenAutomations can export the normalized
648
690
  event envelope and OpenLoops can consume it through the existing generic event
649
- handler:
691
+ route:
650
692
 
651
693
  ```bash
652
694
  automations --json webhooks event <route> --body-json '<json>' \
653
- | loops --json events handle generic
695
+ | loops --json routes create generic
654
696
  ```
655
697
 
656
698
  This is not automation materialization in OpenLoops. It is an explicit
657
699
  event-envelope workflow handoff: OpenAutomations owns deterministic automation
658
700
  specs, webhook normalization, queue state, approvals, DLQ, and replay; OpenLoops
659
701
  owns agent workflow invocation after the operator routes the envelope to
660
- `loops events handle generic`.
702
+ `loops routes create generic`.
661
703
 
662
704
  Do not store automation specs in OpenLoops, infer automation triggers from event
663
705
  transport alone, or replace the OpenAutomations queue with loop/workflow rows.
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bun
2
+ import type { LoopStorageContract } from "../lib/storage/contract.js";
3
+ export declare function apiStatus(): {
4
+ ok: boolean;
5
+ service: string;
6
+ status: import("../index.js").LoopDeploymentStatus;
7
+ };
8
+ export interface LoopsApiServerOptions {
9
+ host?: string;
10
+ port?: number;
11
+ storage?: LoopStorageContract;
12
+ bodyLimitBytes?: number;
13
+ evidenceLimitBytes?: number;
14
+ now?: () => Date;
15
+ }
16
+ export declare function createLoopsApiServer(opts?: LoopsApiServerOptions): Bun.Server<undefined>;
17
+ export declare function main(argv?: string[]): Promise<void>;