@hasna/loops 0.4.10 → 0.4.12

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,99 @@ 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.12 (2026-07-05)
9
+
10
+ Drain throughput: `--max-active` is now a per-route ceiling instead of a
11
+ store-wide one, drain dispatch spreads across subscription accounts by live load,
12
+ and every step records the account it ran on.
13
+
14
+ ### Fixed
15
+
16
+ - **Route admission — per-route `--max-active`:** the global admission count was
17
+ store-wide, so the busiest router's `--max-active` became a shared ceiling that
18
+ throttled every other router (average concurrency ~1.9 while the agent lane
19
+ allowed 12; ~253 items/24h deferred as `global active workflow limit reached`).
20
+ The global count is now scoped to the route/drain that set the limit; scope
21
+ precedence is an explicit `--max-active-scope <key>` first, then the running
22
+ loop's `LOOPS_LOOP_NAME`, then the route key as the final fallback — so each
23
+ router's `--max-active` is its own ceiling.
24
+ Existing routers get correct scoping with no config change. `project` and
25
+ `project_group` counts are unchanged: they remain cross-route anti-hog caps.
26
+ - **Per-account attribution:** Codewith agent steps carry their subscription
27
+ account in `authProfile`, not an `AccountRef`, so `workflow_step_runs.account_profile`
28
+ was NULL for every drain worker and per-account burn could not be attributed.
29
+ The resolved auth profile (and provider as `account_tool`) is now recorded on
30
+ each step at run creation, and the per-role assignment is surfaced in drain
31
+ reports.
32
+
33
+ ### Added
34
+
35
+ - **Least-loaded auth-profile pool selection + `--max-per-profile`:** pooled
36
+ Codewith dispatch picked a member by a pure hash of the work item, which stacked
37
+ several concurrent workers on one account at high concurrency and tripped
38
+ provider-side 429/stream-drop limits. Live drains now count running steps per
39
+ account and place each route on the least-loaded pool member (verifier/planner
40
+ still land on a different account than the worker; the hash is the deterministic
41
+ tie-break, so a cold store reproduces the prior assignment). New
42
+ `--max-per-profile <K>` (default 2 for pools of two or more, `0` disables) defers
43
+ a route when every pool account already has `K` running steps.
44
+
45
+ Schema: additive sqlite migration `0008_work_item_route_scope` (nullable
46
+ `workflow_work_items.route_scope`; its index is created only by the migration —
47
+ never in the baseline DDL, which re-runs on every open and would crash pre-0008
48
+ databases). `SCHEMA_USER_VERSION` is intentionally not bumped so an older binary
49
+ can still open a database this migration touched (rollback-safe). Postgres gets
50
+ the equivalent additive migration `0004_work_item_route_scope`; the released,
51
+ checksummed `0002_workflows_goals` block is untouched so existing postgres
52
+ ledgers keep verifying. Upgrade coverage: a real pre-0008 (0.4.11 schema)
53
+ fixture database must open cleanly, gain the column + index, and keep its data;
54
+ released postgres migration checksums are pinned in tests.
55
+
56
+ ## 0.4.11 (2026-07-05)
57
+
58
+ Scheduler fairness and PR-route hygiene: fast command loops no longer starve
59
+ behind long agent workers, PR tasks dedupe by GitHub identity, and merged/closed
60
+ PR routes close themselves out of the queue.
61
+
62
+ ### Fixed
63
+
64
+ - **Daemon scheduler — separated concurrency lanes:** the daemon shared a single
65
+ concurrency pool, so long agent/workflow workers (minutes to over an hour) could
66
+ occupy every slot and starve fast command loops (monitors, digests, syncs) —
67
+ even the merge router starved. Command-target and agent/workflow-target loops
68
+ now draw from independent claim budgets. New env `LOOPS_DAEMON_COMMAND_CONCURRENCY`
69
+ (default 4) and `LOOPS_DAEMON_AGENT_CONCURRENCY` (default 8); the legacy
70
+ `LOOPS_DAEMON_CONCURRENCY` / `--concurrency` still work as the agent-lane knob for
71
+ back-compat, and new `--command-concurrency` / `--agent-concurrency` daemon flags
72
+ are added. A saturated lane no longer consumes the other lane's budget.
73
+ - **Todos-task routing — PR fingerprint dedupe:** the repos registry maps several
74
+ local checkouts to one GitHub repo, so a single PR was minted as 2-3 duplicate
75
+ todos tasks (distinct ids + checkout paths) that each dispatched a full worker.
76
+ PR-subject tasks now dedupe by a stable `owner/repo#number` fingerprint (from an
77
+ explicit PR fingerprint field or a canonical PR URL / `github-pr:` handle,
78
+ lowercased) instead of the `(source-path, task-id)` key, collapsing the
79
+ duplicates to one work item. Non-PR tasks keep the path/id key (no false dedupe).
80
+ - **Todos-task routing — freshness skip closes the task:** 0.4.10's freshness gate
81
+ stopped dispatching a worker for a merged/closed PR but left the task pending +
82
+ route-opted-in, so every drain tick re-skipped it forever. On a definitive
83
+ MERGED/CLOSED freshness skip the drain now marks the source task done and strips
84
+ its `auto:route`/`route:enabled` opt-in so it leaves the queue. Only fires when
85
+ gh/metadata definitively reports MERGED/CLOSED; never on dry-run.
86
+
87
+ ## 0.4.10 (2026-07-05)
88
+
89
+ Hotfix bundle for the PR-merge drain pipeline.
90
+
91
+ ### Fixed
92
+
93
+ - **Todos-task drain:** re-admit a terminal work item whose todos task is still
94
+ actionable (bounded by a redispatch cap + per-attempt backoff) instead of
95
+ deduping it away forever, so real task work keeps dispatching.
96
+ - **PR review gate:** freshness gate skips merge/review routes for already
97
+ merged/closed PRs; normalize `app/<slug>` and `<slug>[bot]` bot logins so
98
+ bot-authored PRs resolve an author instead of hard-failing the format check.
99
+ - **Runtime/store:** cap persisted run stdout/stderr; project path resolution fix.
100
+
8
101
  ## 0.4.9 (2026-07-04)
9
102
 
10
103
  Unblock the PR-merge pipeline: task-lifecycle/route workers now dispatch real
package/README.md CHANGED
@@ -594,8 +594,15 @@ This is the intended deterministic-to-agentic path: a producer creates a todos
594
594
  task, `@hasna/events` delivers `task.created`, OpenLoops records the invocation
595
595
  and admission item, OpenLoops creates a worker/verifier workflow when admitted,
596
596
  and the workflow updates todos with evidence. Use account pools so worker and
597
- verifier steps do not burn the same profile; OpenLoops picks deterministically
598
- and uses a different verifier profile when the pool has at least two entries.
597
+ verifier steps do not burn the same profile. For live Codewith drains, OpenLoops
598
+ spreads each route to the **least-loaded** pool account (counting running steps
599
+ per account) and still keeps the verifier on a different profile than the worker;
600
+ the deterministic hash is the tie-break, so a cold store reproduces the previous
601
+ assignment exactly. `--max-per-profile <K>` (default 2 for pools of two or more,
602
+ `0` to disable) defers a route when every pool account already has `K` running
603
+ steps — the guardrail that keeps higher concurrency from stacking on one
604
+ subscription and tripping provider-side rate limits. The chosen account per role
605
+ is recorded on each step (`account_profile`) and surfaced in drain reports.
599
606
  Use `--dry-run` to inspect the rendered invocation, work item, workflow, and
600
607
  loop input without storing anything.
601
608
 
@@ -645,11 +652,21 @@ loops routes schedule todos-task oss-task-route-drain \
645
652
  --max-active-per-project 1 \
646
653
  --max-active-per-project-group 4 \
647
654
  --max-active 12 \
655
+ --max-per-profile 2 \
648
656
  --worktree-mode required \
649
657
  --evidence-dir "$HOME/.hasna/loops/reports/oss-task-route-drain" \
650
658
  --compact
651
659
  ```
652
660
 
661
+ `--max-active` counts active routed workflows **per route**. Scope precedence:
662
+ an explicit `--max-active-scope <key>` wins, else the running loop's
663
+ `LOOPS_LOOP_NAME`, else the route key — so each router's limit is its own
664
+ ceiling rather than a store-wide total shared by every router.
665
+ `--max-active-per-project` and `--max-active-per-project-group` remain cross-route
666
+ anti-hog caps counted over all routes. Raise a router's `--max-active`
667
+ deliberately once counting is per-route; keep `--max-per-profile` set so the
668
+ extra concurrency spreads across subscription accounts.
669
+
653
670
  Only route tasks that explicitly opt in with `auto:route`, `route_enabled=true`,
654
671
  or `automation.allowed=true`. Use Codewith account pools, required worktrees,
655
672
  max-active throttles, and bounded evidence directories. Do not dispatch or paste
package/dist/api/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@hasna/loops",
6
- version: "0.4.10",
6
+ version: "0.4.12",
7
7
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
8
8
  type: "module",
9
9
  main: "dist/index.js",