@hasna/loops 0.3.38 → 0.3.40

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/dist/types.d.ts CHANGED
@@ -43,6 +43,26 @@ export type ScheduleSpec = OnceSchedule | IntervalSchedule | CronSchedule | Dyna
43
43
  export interface RuntimePreflightPolicy {
44
44
  beforeRun?: boolean;
45
45
  }
46
+ export interface OpenAutomationsRuntimeBinding {
47
+ integration: "open-automations";
48
+ role: "runtime";
49
+ handoff: "claim-queue";
50
+ queueOwner: "open-automations";
51
+ runtimeOwner: "open-loops";
52
+ statusCommand: "automations status";
53
+ claimCommand: "automations queue claim";
54
+ completeCommand: "automations queue complete";
55
+ failCommand: "automations queue fail";
56
+ eventHandoff: {
57
+ envelopeCommand: "automations webhooks event";
58
+ handlerCommand: "loops events handle generic";
59
+ pipeExample: string;
60
+ boundary: string;
61
+ };
62
+ requiredEnvironment: string[];
63
+ guarantees: string[];
64
+ nonGoals: string[];
65
+ }
46
66
  export interface CommandTarget {
47
67
  type: "command";
48
68
  command: string;
@@ -51,6 +71,7 @@ export interface CommandTarget {
51
71
  shell?: boolean;
52
72
  env?: Record<string, string>;
53
73
  timeoutMs?: number;
74
+ idleTimeoutMs?: number;
54
75
  account?: AccountRef;
55
76
  preflight?: RuntimePreflightPolicy;
56
77
  }
@@ -94,6 +115,7 @@ export interface AgentTarget {
94
115
  authProfile?: string;
95
116
  extraArgs?: string[];
96
117
  timeoutMs?: number;
118
+ idleTimeoutMs?: number;
97
119
  configIsolation?: AgentConfigIsolation;
98
120
  permissionMode?: AgentPermissionMode;
99
121
  sandbox?: AgentSandbox;
@@ -115,6 +137,90 @@ export type LoopTarget = ExecutableTarget | WorkflowTarget;
115
137
  export type WorkflowStatus = "active" | "archived";
116
138
  export type WorkflowRunStatus = "running" | "succeeded" | "failed" | "timed_out" | "cancelled";
117
139
  export type WorkflowStepRunStatus = "pending" | "running" | "succeeded" | "failed" | "timed_out" | "skipped" | "cancelled";
140
+ export type WorkflowInvocationSourceKind = "task" | "event" | "schedule" | "manual" | "pr" | "review" | "knowledge";
141
+ export type WorkflowInvocationSubjectKind = "repo" | "pr" | "task" | "doc" | "run" | "metric";
142
+ export type WorkflowInvocationIntent = "route" | "mutate" | "review" | "evaluate" | "report";
143
+ export interface WorkflowInvocationRef {
144
+ kind: string;
145
+ id?: string;
146
+ path?: string;
147
+ url?: string;
148
+ dedupeKey?: string;
149
+ raw?: Record<string, unknown>;
150
+ }
151
+ export interface WorkflowInvocationScope {
152
+ projectPath?: string;
153
+ projectGroup?: string;
154
+ worktreePolicy?: AgentWorktreeMode;
155
+ permissions?: string;
156
+ accountPolicy?: string;
157
+ concurrencyGroup?: string;
158
+ [key: string]: unknown;
159
+ }
160
+ export interface WorkflowInvocationOutputPolicy {
161
+ report?: "always" | "on_change" | "on_failure";
162
+ createTask?: "never" | "on_actionable" | "on_failure" | "always";
163
+ [key: string]: unknown;
164
+ }
165
+ export interface WorkflowInvocation {
166
+ id: string;
167
+ workflowId?: string;
168
+ templateId?: string;
169
+ sourceRef: WorkflowInvocationRef;
170
+ subjectRef: WorkflowInvocationRef;
171
+ intent: WorkflowInvocationIntent;
172
+ scope?: WorkflowInvocationScope;
173
+ outputPolicy?: WorkflowInvocationOutputPolicy;
174
+ createdAt: string;
175
+ updatedAt: string;
176
+ }
177
+ export interface CreateWorkflowInvocationInput {
178
+ id?: string;
179
+ workflowId?: string;
180
+ templateId?: string;
181
+ sourceRef: WorkflowInvocationRef;
182
+ subjectRef: WorkflowInvocationRef;
183
+ intent: WorkflowInvocationIntent;
184
+ scope?: WorkflowInvocationScope;
185
+ outputPolicy?: WorkflowInvocationOutputPolicy;
186
+ }
187
+ export type WorkflowWorkItemStatus = "queued" | "deferred" | "admitted" | "running" | "succeeded" | "failed" | "dead_letter" | "cancelled";
188
+ export interface WorkflowWorkItem {
189
+ id: string;
190
+ routeKey: string;
191
+ idempotencyKey: string;
192
+ invocationId: string;
193
+ sourceType: string;
194
+ sourceRef: string;
195
+ subjectRef: string;
196
+ projectKey?: string;
197
+ projectGroup?: string;
198
+ priority: number;
199
+ status: WorkflowWorkItemStatus;
200
+ attempts: number;
201
+ nextAttemptAt?: string;
202
+ leaseExpiresAt?: string;
203
+ workflowId?: string;
204
+ loopId?: string;
205
+ workflowRunId?: string;
206
+ lastReason?: string;
207
+ createdAt: string;
208
+ updatedAt: string;
209
+ }
210
+ export interface UpsertWorkflowWorkItemInput {
211
+ routeKey: string;
212
+ idempotencyKey: string;
213
+ invocationId: string;
214
+ sourceType: string;
215
+ sourceRef: string;
216
+ subjectRef: string;
217
+ projectKey?: string;
218
+ projectGroup?: string;
219
+ priority?: number;
220
+ status?: Extract<WorkflowWorkItemStatus, "queued" | "deferred">;
221
+ nextAttemptAt?: string;
222
+ lastReason?: string;
223
+ }
118
224
  export interface WorkflowStep {
119
225
  id: string;
120
226
  name?: string;
@@ -164,8 +270,11 @@ export interface WorkflowRun {
164
270
  workflowName: string;
165
271
  loopId?: string;
166
272
  loopRunId?: string;
273
+ invocationId?: string;
274
+ workItemId?: string;
167
275
  scheduledFor?: string;
168
276
  idempotencyKey?: string;
277
+ manifestPath?: string;
169
278
  goalRunId?: string;
170
279
  status: WorkflowRunStatus;
171
280
  startedAt?: string;
package/docs/USAGE.md CHANGED
@@ -108,7 +108,7 @@ loops create agent supply-chain-watch \
108
108
  --provider codewith \
109
109
  --every 15m \
110
110
  --cwd /path/to/repo \
111
- --sandbox danger-full-access \
111
+ --sandbox workspace-write \
112
112
  --prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
113
113
  ```
114
114
 
@@ -120,7 +120,7 @@ loops create agent supply-chain-watch \
120
120
  --auth-profile account001 \
121
121
  --every 15m \
122
122
  --cwd /path/to/repo \
123
- --sandbox danger-full-access \
123
+ --sandbox workspace-write \
124
124
  --prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
125
125
  ```
126
126
 
@@ -216,6 +216,9 @@ Built-in templates turn common orchestration flows into reusable workflow JSON.
216
216
  `event-worker-verifier` handles any Hasna event envelope and then verifies the
217
217
  handling. `bounded-agent-worker-verifier` is for recurring bounded agent work:
218
218
  one worker runs a narrow objective, then a fresh verifier audits the result.
219
+ The catalog also includes `task-lifecycle`, `pr-review`, `scheduled-audit`,
220
+ `knowledge-refresh`, `report-only`, `incident-response`, and
221
+ `deterministic-check-create-task` for common operator workflows.
219
222
 
220
223
  ```bash
221
224
  loops templates list
@@ -225,7 +228,7 @@ loops templates render todos-task-worker-verifier \
225
228
  --var projectPath=/path/to/repo \
226
229
  --var provider=codewith \
227
230
  --var authProfilePool=account004,account005,account006 \
228
- --var sandbox=danger-full-access
231
+ --var sandbox=workspace-write
229
232
  loops templates create-workflow todos-task-worker-verifier \
230
233
  --var taskId=<task-id> \
231
234
  --var projectPath=/path/to/repo
@@ -240,7 +243,13 @@ loops templates render bounded-agent-worker-verifier \
240
243
  --var projectPath=/path/to/repo \
241
244
  --var provider=codewith \
242
245
  --var authProfilePool=account004,account005 \
243
- --var sandbox=danger-full-access
246
+ --var sandbox=workspace-write
247
+ loops templates render pr-review \
248
+ --var prUrl=https://github.com/hasna/loops/pull/123 \
249
+ --var projectPath=/path/to/repo
250
+ loops templates render deterministic-check-create-task \
251
+ --var projectPath=/path/to/repo \
252
+ --var checkCommand='your deterministic check and todos upsert command'
244
253
  ```
245
254
 
246
255
  Repo-mutating task/event routes should set `worktreeMode=required` so the
@@ -267,15 +276,16 @@ non-git/non-mutating project is expected and the fallback is recorded. Use
267
276
  `worktreeBranchPrefix` can override the storage root and branch prefix.
268
277
 
269
278
  For event-driven task automation, `loops events handle todos-task` reads a
270
- Hasna event envelope from stdin or `HASNA_EVENT_JSON`, renders the template, and
271
- schedules a deduped one-shot workflow loop:
279
+ Hasna event envelope from stdin or `HASNA_EVENT_JSON`, records a
280
+ `WorkflowInvocation`, upserts an admission work item, and admits that work item
281
+ into a deduped one-shot workflow loop when route capacity allows:
272
282
 
273
283
  ```bash
274
284
  cat task-created-event.json | loops events handle todos-task \
275
285
  --provider codewith \
276
286
  --auth-profile-pool account004,account005,account006 \
277
287
  --permission-mode bypass \
278
- --sandbox danger-full-access \
288
+ --sandbox workspace-write \
279
289
  --worktree-mode required
280
290
  ```
281
291
 
@@ -300,19 +310,40 @@ cat task-created-event.json | loops events handle todos-task \
300
310
  --max-active 12
301
311
  ```
302
312
 
303
- The limits count active routed worker/verifier workflow loops once per workflow.
313
+ The limits count active admitted/running OpenLoops work items once per workflow.
304
314
  `--max-active-per-project` gates new work for the same project path,
305
315
  `--max-active-per-project-group` shares a pool across related projects such as
306
316
  `oss`, and `--max-active` is the global routed-workflow cap. Project matching
307
317
  uses the canonical git top-level path when available, so repo subdirectories
308
- share the same project cap. A throttled event is skipped with JSON evidence and
309
- `queuedAtSource=true` instead of creating another worker loop; the source task
310
- remains the durable queue item and should be replayed/drained later by the task
311
- scheduler. Re-delivering the event later is safe because event handlers dedupe
312
- by task/event id before rendering worktree plans or checking route limits. In
318
+ share the same project cap. A throttled event records a deferred OpenLoops
319
+ admission work item with JSON evidence instead of creating another worker loop.
320
+ Re-delivering the event later is safe because handlers dedupe by the work-item
321
+ idempotency key before rendering worktree plans or checking route limits. In
313
322
  dry-run mode, throttle counts are not evaluated because opening the live loop
314
323
  store can create or migrate the local database.
315
324
 
325
+ Inspect route state with:
326
+
327
+ ```bash
328
+ cat task-created-event.json | loops routes preview todos-task --sandbox workspace-write
329
+ cat task-created-event.json | loops routes create todos-task --sandbox workspace-write
330
+ loops routes drain todos-task --task-list oss --max-dispatch 2 --compact
331
+ loops routes schedule todos-task route-drain-oss-5m --every 5m --task-list oss --max-dispatch 1 --compact
332
+ loops routes list --route-key todos-task
333
+ loops routes show <work-item-id>
334
+ loops routes invocations
335
+ ```
336
+
337
+ When a workflow run starts from an admitted work item, OpenLoops writes a
338
+ manifest under:
339
+
340
+ ```text
341
+ .hasna/loops/runs/<project-slug>/<subject-key>/<run-id>/manifest.json
342
+ ```
343
+
344
+ `subject-key` is a safe derived path segment (`kind-safeSlug-shortHash`), not
345
+ the raw subject reference. The raw `subjectRef` is stored inside the manifest.
346
+
316
347
  When tasks were created while capacity was full, or when bulk producers created
317
348
  many tasks at once, use the drain command instead of replaying every webhook by
318
349
  hand. It scans `todos ready --json`, so tasks with incomplete dependencies,
@@ -357,20 +388,26 @@ cat event.json | loops events handle generic \
357
388
  --provider codewith \
358
389
  --auth-profile-pool account004,account005,account006 \
359
390
  --permission-mode bypass \
360
- --sandbox danger-full-access \
391
+ --sandbox workspace-write \
361
392
  --project-path /path/to/repo \
362
393
  --worktree-mode required
363
394
  ```
364
395
 
365
396
  This is the intended deterministic-to-agentic path: a producer creates a todos
366
- task, `@hasna/events` delivers `task.created`, OpenLoops creates a worker and a
367
- verifier workflow, and the workflow updates todos with evidence. Use account
368
- pools so worker and verifier steps do not burn the same profile; OpenLoops picks
369
- deterministically and uses a different verifier profile when the pool has at
370
- least two entries. Use `--dry-run` to inspect the rendered workflow and loop
371
- input without storing anything, including the worktree path and branch for
397
+ task, `@hasna/events` delivers `task.created`, OpenLoops records the invocation
398
+ and admission item, OpenLoops creates a worker/verifier workflow when admitted,
399
+ and the workflow updates todos with evidence. Use account pools so worker and
400
+ verifier steps do not burn the same profile; OpenLoops picks deterministically
401
+ and uses a different verifier profile when the pool has at least two entries.
402
+ Use `--dry-run` to inspect the rendered invocation, work item, workflow, and
403
+ loop input without storing anything, including the worktree path and branch for
372
404
  git-backed tasks.
373
405
 
406
+ Generated worker/verifier workflows fail closed when `sandbox=danger-full-access`
407
+ is requested without `manualBreakGlass=true`. Use `workspace-write` for
408
+ unattended task/event routes. Full access is an explicit manual emergency path,
409
+ not a default automation mode.
410
+
374
411
  ## Transcript-Driven Loops
375
412
 
376
413
  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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/loops",
3
- "version": "0.3.38",
3
+ "version": "0.3.40",
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",