@hasna/loops 0.3.56 → 0.3.57
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/README.md +188 -14
- package/dist/cli/index.js +1325 -175
- package/dist/daemon/index.js +143 -19
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4031 -2876
- package/dist/lib/health.d.ts +2 -2
- package/dist/lib/store.d.ts +3 -0
- package/dist/lib/store.js +46 -8
- package/dist/lib/templates.d.ts +4 -0
- package/dist/mcp/index.d.ts +12 -0
- package/dist/mcp/index.js +5821 -0
- package/dist/sdk/index.js +134 -16
- package/docs/AUTOMATION_RUNTIME_DESIGN.md +228 -0
- package/docs/USAGE.md +123 -15
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -41,6 +41,41 @@ bun link
|
|
|
41
41
|
|
|
42
42
|
The CLI stores state in `~/.hasna/loops` by default. Set `LOOPS_DATA_DIR` to isolate state for tests or another profile.
|
|
43
43
|
|
|
44
|
+
## MCP Server
|
|
45
|
+
|
|
46
|
+
OpenLoops ships a stdio MCP server for safe loop and workflow inspection from
|
|
47
|
+
MCP-capable agents:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
loops-mcp list-tools
|
|
51
|
+
loops-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The package also exports the server factory for embedded hosts:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createLoopsMcpServer } from "@hasna/loops/mcp";
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Available read tools include `loops_list`, `loops_show`, `loop_runs`,
|
|
61
|
+
`loops_doctor`, `workflows_list`, `workflow_read`, and `workflow_validate`.
|
|
62
|
+
Resources are available at `loops://runtime` and `loops://tools`.
|
|
63
|
+
Those tools use the same `Store`, public redaction helpers, and workflow parser
|
|
64
|
+
as the CLI and SDK, so read output and validation behavior stay aligned across
|
|
65
|
+
surfaces.
|
|
66
|
+
|
|
67
|
+
Mutation tools are disabled by default. Start the server with
|
|
68
|
+
`LOOPS_MCP_ALLOW_MUTATIONS=true` only for a trusted local MCP host that should be
|
|
69
|
+
allowed to change loop state. Even then, mutation tools require exact
|
|
70
|
+
confirmation strings: `loop_pause`, `loop_resume`, `loop_run_now`,
|
|
71
|
+
`loop_create_command`, and `loop_create_workflow`. MCP `loop_run_now` schedules
|
|
72
|
+
the loop for immediate daemon pickup; inline execution remains CLI-only.
|
|
73
|
+
|
|
74
|
+
Keep host-affecting or long-running operations on the CLI: daemon
|
|
75
|
+
start/stop/install/logs, inline `run-now`, `tick`, loop removal/archive
|
|
76
|
+
maintenance, workflow create/migrate/cancel/recover, agent loop creation,
|
|
77
|
+
template materialization, and event-route drains.
|
|
78
|
+
|
|
44
79
|
## Create Loops
|
|
45
80
|
|
|
46
81
|
Run a deterministic command every minute:
|
|
@@ -94,6 +129,20 @@ loops create agent supply-chain-watch \
|
|
|
94
129
|
--prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
|
|
95
130
|
```
|
|
96
131
|
|
|
132
|
+
Run an OpenCode loop with an explicit provider/model. OpenCode reads
|
|
133
|
+
`~/.config/opencode/config.json` when no model is supplied, so OpenLoops rejects
|
|
134
|
+
OpenCode agent targets without `--model` instead of inheriting a stale or
|
|
135
|
+
machine-specific default.
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
loops create agent opencode-smoke \
|
|
139
|
+
--provider opencode \
|
|
140
|
+
--model openrouter/google/gemini-2.5-flash \
|
|
141
|
+
--at "$(date -u -d '+1 minute' +%Y-%m-%dT%H:%M:%SZ)" \
|
|
142
|
+
--cwd /path/to/repo \
|
|
143
|
+
--prompt "Reply with exactly OK."
|
|
144
|
+
```
|
|
145
|
+
|
|
97
146
|
For `codewith` and `aicopilot` account isolation, register matching OpenAccounts tools first if they are not built in on the machine:
|
|
98
147
|
|
|
99
148
|
```bash
|
|
@@ -166,6 +215,11 @@ loops create agent repo-fixer \
|
|
|
166
215
|
|
|
167
216
|
Goal planning and validation use the Vercel AI SDK with `@openrouter/ai-sdk-provider`. Set `OPENROUTER_API_KEY`; optionally set `LOOPS_GOAL_BASE_URL` to point at a local gateway compatible with OpenRouter. Goal context is passed to wrapped commands and agents as `LOOPS_GOAL_ID`, `LOOPS_GOAL_OBJECTIVE`, and `LOOPS_GOAL_NODE_KEY`.
|
|
168
217
|
|
|
218
|
+
If a resumed goal plan has no runnable nodes, failed run output includes a
|
|
219
|
+
structured `diagnostics` object with the concrete blocker and `owner`
|
|
220
|
+
(`goal`, `goal-plan`, or `goal-plan-node`) so scheduled workflow loops do not
|
|
221
|
+
end as opaque blocked plans.
|
|
222
|
+
|
|
169
223
|
Inspect configured and runtime goal state:
|
|
170
224
|
|
|
171
225
|
```bash
|
|
@@ -238,8 +292,8 @@ loops create workflow repo-morning-loop --workflow repo-morning --cron "0 8 * *
|
|
|
238
292
|
|
|
239
293
|
Use `recover` only for interrupted `running` workflow runs whose recorded child
|
|
240
294
|
process is gone. Terminal `timed_out` task/event workflow runs are audit
|
|
241
|
-
history;
|
|
242
|
-
cause.
|
|
295
|
+
history; use `loops routes requeue <work-item-id> --reason "<cause fixed>"`
|
|
296
|
+
after fixing the cause, then redeliver or drain the original task/event route.
|
|
243
297
|
|
|
244
298
|
Workflow specs are stored separately from loops. A loop can schedule a workflow, but workflow runs and step runs have their own durable rows and events. Steps run in dependency order and a scheduled workflow run is idempotent per loop slot.
|
|
245
299
|
|
|
@@ -309,11 +363,14 @@ registry.
|
|
|
309
363
|
|
|
310
364
|
Timeout policy is explicit. Deterministic command/check steps should normally
|
|
311
365
|
keep finite `timeoutMs`/`idleTimeoutMs` guards so broken shell work cannot run
|
|
312
|
-
forever. Agentic work steps default to no timeout in built-in
|
|
313
|
-
and task-lifecycle templates; use `timeoutMs: null` in workflow
|
|
314
|
-
`--timeout none` / `--timeout unlimited` for CLI-created targets, when
|
|
315
|
-
may need hours or days.
|
|
316
|
-
|
|
366
|
+
forever. Agentic work steps default to no wall-clock timeout in built-in
|
|
367
|
+
worker/verifier and task-lifecycle templates; use `timeoutMs: null` in workflow
|
|
368
|
+
JSON, or `--timeout none` / `--timeout unlimited` for CLI-created targets, when
|
|
369
|
+
a step may need hours or days. Verifier/evaluator steps add a 15 minute
|
|
370
|
+
`idleTimeoutMs` watchdog by default so a verifier cannot hang silently after the
|
|
371
|
+
worker finishes; pass `--verifier-idle-timeout none` or template variable
|
|
372
|
+
`verifierIdleTimeoutMs=none` only when another heartbeat is guaranteed. Use a
|
|
373
|
+
positive numeric `timeoutMs` only when an agentic step is intentionally bounded.
|
|
317
374
|
|
|
318
375
|
To migrate existing workflow loops, do not edit `workflow_specs.steps_json`
|
|
319
376
|
directly because historical workflow runs must keep pointing at their original
|
|
@@ -329,7 +386,8 @@ with the requested agent timeout policy, retargets only future executions of
|
|
|
329
386
|
eligible non-running workflow loops, and leaves terminal timed-out workflow runs
|
|
330
387
|
as audit history. Use `loops workflows recover` only for interrupted `running`
|
|
331
388
|
workflow runs whose recorded child process is gone; terminal `timed_out` runs
|
|
332
|
-
must be requeued
|
|
389
|
+
must be requeued with `loops routes requeue <work-item-id> --reason "<cause fixed>"` before
|
|
390
|
+
re-delivering or draining the original task/event route.
|
|
333
391
|
|
|
334
392
|
```json
|
|
335
393
|
{
|
|
@@ -402,15 +460,60 @@ cat task-created-event.json | loops events handle todos-task \
|
|
|
402
460
|
--worktree-mode required
|
|
403
461
|
```
|
|
404
462
|
|
|
463
|
+
Task routing is explicit opt-in. The handler skips the event without creating a
|
|
464
|
+
workflow unless the event data or metadata has `route_enabled=true`,
|
|
465
|
+
`automation.allowed=true`, or a task tag containing `auto:route`. It also skips
|
|
466
|
+
blocked, completed/done, cancelled/canceled, failed, archived, manual,
|
|
467
|
+
approval-required, or `no-auto` tasks. Terminal route work items such as
|
|
468
|
+
failed, dead-letter, cancelled, or succeeded history stay deduped until an
|
|
469
|
+
operator runs `loops routes requeue <work-item-id> --reason "<cause fixed>"`.
|
|
470
|
+
The next route-created output records `requeue` evidence with the previous work
|
|
471
|
+
item id, previous attempts, operator reason, new attempt, workflow id, and loop
|
|
472
|
+
id.
|
|
473
|
+
|
|
474
|
+
Task route drains can select providers from task metadata instead of running one
|
|
475
|
+
fixed provider/account pool for the whole drain. Add one or more
|
|
476
|
+
`--provider-rule field=value:provider[:profile1,profile2]` flags; the first
|
|
477
|
+
matching rule wins. Rule profiles become a Codewith auth-profile pool for
|
|
478
|
+
`provider=codewith` and an OpenAccounts account pool for other providers. Tasks
|
|
479
|
+
can also carry `provider_hint`/`route_provider`, `auth_profile_pool`, or
|
|
480
|
+
`account_pool` metadata. Dry-run, drain evidence, and route invocation scope
|
|
481
|
+
include `providerRouting` so operators can see why a provider/account was
|
|
482
|
+
selected.
|
|
483
|
+
|
|
484
|
+
```bash
|
|
485
|
+
loops routes drain todos-task \
|
|
486
|
+
--dry-run \
|
|
487
|
+
--provider-rule area=frontend:claude:claude-ui-a,claude-ui-b \
|
|
488
|
+
--provider-rule area=backend:codewith:account004,account005 \
|
|
489
|
+
--worktree-mode required
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
PR approval or merge tasks that need a branch-protection review must carry
|
|
493
|
+
explicit non-author GitHub reviewer evidence before a worker is created. When a
|
|
494
|
+
task has a PR reference plus `reviewDecision=REVIEW_REQUIRED`,
|
|
495
|
+
`mergeStateStatus=BLOCKED`, branch-protection review language, or similar
|
|
496
|
+
approval/merge intent, routing is skipped unless `--github-reviewer`,
|
|
497
|
+
`--github-reviewer-pool`, or task metadata such as `github_reviewer` /
|
|
498
|
+
`github_reviewer_pool` names at least one GitHub login different from the PR
|
|
499
|
+
author. This blocks self-review routes before they can spawn an impossible
|
|
500
|
+
worker, and dry-run/admission JSON includes `prReviewRouting` evidence.
|
|
501
|
+
|
|
405
502
|
By default, `todos-task` routes use `todos-task-worker-verifier` for backwards
|
|
406
503
|
compatibility. Use `--template task-lifecycle` to run the full triage ->
|
|
407
504
|
planner -> worker -> verifier lifecycle. The route rejects unrelated templates
|
|
408
505
|
such as `pr-review` so a todos task cannot accidentally use the wrong contract.
|
|
506
|
+
The default worker/verifier template starts with a deterministic
|
|
507
|
+
`source-task-gate` command that runs `todos --project <source-store> --json
|
|
508
|
+
inspect <task-id>` before the worker. If the routed source task cannot be
|
|
509
|
+
resolved in the intended Todos store, the workflow fails before repo-mutating
|
|
510
|
+
agent work starts.
|
|
409
511
|
The lifecycle template adds deterministic gates after triage and planning. If
|
|
410
512
|
either step marks the task blocked, omits its contextual
|
|
411
513
|
`openloops:triage=go task=<id> event=<event-id>` /
|
|
412
514
|
`openloops:planner=go task=<id> event=<event-id>` marker comment, or the task
|
|
413
|
-
is no-auto/manual/
|
|
515
|
+
is blocked/completed/done/cancelled/failed/archived/no-auto/manual/
|
|
516
|
+
approval-required, the worker step is not started. Use
|
|
414
517
|
`--triage-auth-profile`, `--planner-auth-profile`, `--worker-auth-profile`, and
|
|
415
518
|
`--verifier-auth-profile` when exact Codewith profiles are needed, or use
|
|
416
519
|
`--auth-profile-pool` for deterministic role rotation.
|
|
@@ -460,6 +563,7 @@ loops routes drain todos-task --task-list oss --max-dispatch 2 --compact
|
|
|
460
563
|
loops routes schedule todos-task route-drain-oss-5m --every 5m --task-list oss --max-dispatch 1 --compact
|
|
461
564
|
loops routes list --route-key todos-task
|
|
462
565
|
loops routes show <work-item-id>
|
|
566
|
+
loops routes requeue <work-item-id> --reason "fixed upstream blocker"
|
|
463
567
|
loops routes invocations
|
|
464
568
|
```
|
|
465
569
|
|
|
@@ -549,6 +653,72 @@ When a loop or workflow is used for execution, keep `HASNA_AUTOMATIONS_DIR`
|
|
|
549
653
|
pointing at the owning OpenAutomations data root and preserve the runner id in
|
|
550
654
|
completion/failure calls so OpenAutomations can enforce action leases.
|
|
551
655
|
|
|
656
|
+
### Planned Workflow Upsert SDK
|
|
657
|
+
|
|
658
|
+
External compilers such as `@hasna/actions` and `@hasna/automations` should not
|
|
659
|
+
write OpenLoops SQLite rows directly. The stable contract should be an
|
|
660
|
+
idempotent CLI/SDK upsert that accepts a fully rendered one-shot workflow loop
|
|
661
|
+
request and returns durable refs.
|
|
662
|
+
|
|
663
|
+
Proposed SDK shape:
|
|
664
|
+
|
|
665
|
+
```ts
|
|
666
|
+
type WorkflowUpsertRequest = {
|
|
667
|
+
idempotencyKey: string;
|
|
668
|
+
source: { kind: "action" | "automation" | "event"; id: string; dedupeKey?: string };
|
|
669
|
+
subject: { kind: "repo" | "task" | "pr" | "run"; id?: string; path?: string; url?: string };
|
|
670
|
+
workflow: { name: string; description?: string; steps: WorkflowStepInput[] };
|
|
671
|
+
loop: { name: string; schedule: { type: "once"; at: string }; machine?: LoopMachineRef };
|
|
672
|
+
route?: { projectPath?: string; projectGroup?: string; concurrencyGroup?: string };
|
|
673
|
+
execution?: AutomationExecutionPolicy;
|
|
674
|
+
mode?: "dry-run" | "preflight" | "commit";
|
|
675
|
+
dispatch?: "schedule" | "run-now" | "none";
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
type WorkflowUpsertResult = {
|
|
679
|
+
ok: boolean;
|
|
680
|
+
dryRun: boolean;
|
|
681
|
+
idempotencyKey: string;
|
|
682
|
+
specHash: string;
|
|
683
|
+
refs: {
|
|
684
|
+
workflowId?: string;
|
|
685
|
+
loopId?: string;
|
|
686
|
+
invocationId?: string;
|
|
687
|
+
workItemId?: string;
|
|
688
|
+
runId?: string;
|
|
689
|
+
manifestPath?: string;
|
|
690
|
+
};
|
|
691
|
+
action: "created" | "updated" | "reused" | "rejected";
|
|
692
|
+
preflight?: { ok: boolean; checks: unknown[]; error?: string };
|
|
693
|
+
};
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
Required semantics:
|
|
697
|
+
|
|
698
|
+
- `mode="dry-run"` validates, canonicalizes, hashes, and returns the same JSON
|
|
699
|
+
shape without mutating OpenLoops state.
|
|
700
|
+
- `mode="preflight"` additionally checks provider binaries, machine routing,
|
|
701
|
+
accounts/auth profiles, prompt files, and workflow target compatibility before
|
|
702
|
+
commit.
|
|
703
|
+
- `mode="commit"` is idempotent on `idempotencyKey` plus `specHash`: identical
|
|
704
|
+
requests return existing refs; changed specs create a new workflow version or
|
|
705
|
+
one-shot loop while preserving previous run history.
|
|
706
|
+
- `dispatch="schedule"` stores a one-shot loop for the daemon; `run-now` claims
|
|
707
|
+
an immediate manual slot; `none` only materializes refs for another owner to
|
|
708
|
+
trigger later.
|
|
709
|
+
- All persisted output is redacted before storage, and returned refs are enough
|
|
710
|
+
for the caller to inspect, cancel, replay, or resolve the run without querying
|
|
711
|
+
SQLite directly.
|
|
712
|
+
|
|
713
|
+
See `docs/AUTOMATION_RUNTIME_DESIGN.md` for the planned DLQ/dead-letter
|
|
714
|
+
lifecycle, including `loops dlq list/show/replay/resolve`, idempotent replay
|
|
715
|
+
keys, and compatibility rules for `@hasna/actions`.
|
|
716
|
+
|
|
717
|
+
The same design doc also defines the planned strict automation execution mode:
|
|
718
|
+
minimal env inheritance, scoped secret refs, enforced allowlists,
|
|
719
|
+
redaction-before-persistence, and provider-safe defaults for automation-created
|
|
720
|
+
work.
|
|
721
|
+
|
|
552
722
|
## Transcript-Driven Loops
|
|
553
723
|
|
|
554
724
|
OpenLoops can turn long-form media or meeting transcripts into recurring workflow work when paired with `iapp-transcriber`. The template at `docs/workflows/transcript-feedback-to-loops.json` transcribes an authorized media URL, asks an agent to extract recurring loop candidates, authors workflow specs, and validates generated workflows before scheduling. Copy it into the target repo, replace `/path/to/repo` with that repo's absolute path, and provide `TRANSCRIBER_SOURCE_URL` through the runner environment or a private, uncommitted workflow copy before storing or scheduling it. Do not commit private or signed media URLs.
|
|
@@ -608,10 +778,14 @@ directory stay as tasks and record an `auto_route_skipped_reason`. Without
|
|
|
608
778
|
`--auto-route`, the command only creates or updates deduped tasks.
|
|
609
779
|
|
|
610
780
|
`hygiene names` reports canonical `machine-*` or `repo-<name>-*` loop names and
|
|
611
|
-
renames only with `--apply`.
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
781
|
+
renames only with `--apply`. Cadence/timer suffixes such as `-5m`, `-15m`,
|
|
782
|
+
`-6h`, `-hourly`, and `-daily` are removed from canonical names; cadence belongs
|
|
783
|
+
in the schedule metadata and the human `loops list` `cadence=` column. Apply
|
|
784
|
+
mode writes a SQLite backup under `<LOOPS_DATA_DIR>/backups` before changing loop
|
|
785
|
+
names. New loops get a compact default description with Why/How/Outcome text
|
|
786
|
+
unless the operator supplies `--description`. `hygiene duplicates` groups loops
|
|
787
|
+
with the same normalized name, cwd, and schedule. `hygiene scripts` inventories
|
|
788
|
+
loops whose command still references `~/.hasna/loops/scripts`.
|
|
615
789
|
`hygiene route-tasks` upserts deduped Todos tasks for hygiene findings with
|
|
616
790
|
stable fingerprints and `no_tmux_dispatch=true` metadata. Route commands use a
|
|
617
791
|
package-managed cursor under `<LOOPS_DATA_DIR>/route-cursors.json` so bounded
|
|
@@ -694,7 +868,7 @@ The adapters intentionally use provider command surfaces instead of pretending e
|
|
|
694
868
|
|
|
695
869
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
696
870
|
- Codewith uses `codewith --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories.
|
|
697
|
-
- AI Copilot and OpenCode use `run --format json --pure`.
|
|
871
|
+
- AI Copilot and OpenCode use `run --format json --pure`. OpenCode requires an explicit provider/model id because ambient OpenCode config is machine-specific.
|
|
698
872
|
- Cursor is CLI-first for now via the standalone `agent -p` binary. OpenLoops no longer falls back to `cursor agent`; install the standalone Cursor Agent CLI so preflight and scheduled runs use the same executable.
|
|
699
873
|
- Codex uses `codex --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
|
|
700
874
|
- Agent prompts are sent through child stdin instead of argv so prompt bodies do not appear in process listings.
|