pi-legion 2.0.2 → 2.0.3
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 +16 -1
- package/docs/legion-v2-contract.md +1 -1
- package/package.json +1 -1
- package/src/legion/budget.ts +5 -3
- package/src/legion/nodes.ts +7 -0
- package/src/runs/orchestrator.ts +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,18 @@ summary so you can see **2.x vs 0.16** without leaving the tree.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [2.0.3] — 2026-10-15
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- `maxChildrenPerNode` only counts **live/running** children; `retire` (including
|
|
19
|
+
Already-retired no-op) frees the budget slot so a full tree can launch again
|
|
20
|
+
after retire. Session keep for resume does **not** occupy the concurrent slot
|
|
21
|
+
(#142 / #143).
|
|
22
|
+
- Header tok/s uses `MIN_RATE_GENERATION_MS=1000` (same floor as tree rows): when
|
|
23
|
+
the in-window generation delta is under 1s, fall back to wall-clock so tiny
|
|
24
|
+
`dGen` cannot explode rates into the millions (#140 / #141).
|
|
25
|
+
|
|
14
26
|
## [2.0.2] — 2026-10-16
|
|
15
27
|
|
|
16
28
|
### Fixed
|
|
@@ -131,6 +143,9 @@ branch — pin `pi-legion@0.16.x` if you still need that model. See
|
|
|
131
143
|
Earlier 0.11–0.15 history (including the `pi-herdr-swarm` /
|
|
132
144
|
`@zzjcool/pi-herdr-subagents` renames) lives in git tags and older Releases.
|
|
133
145
|
|
|
134
|
-
[Unreleased]: https://github.com/zzjcool/pi-legion/compare/v2.0.
|
|
146
|
+
[Unreleased]: https://github.com/zzjcool/pi-legion/compare/v2.0.3...HEAD
|
|
147
|
+
[2.0.3]: https://github.com/zzjcool/pi-legion/compare/v2.0.2...v2.0.3
|
|
148
|
+
[2.0.2]: https://github.com/zzjcool/pi-legion/compare/v2.0.1...v2.0.2
|
|
149
|
+
[2.0.1]: https://github.com/zzjcool/pi-legion/compare/v2.0.0...v2.0.1
|
|
135
150
|
[2.0.0]: https://github.com/zzjcool/pi-legion/releases/tag/v2.0.0
|
|
136
151
|
[0.16.x]: https://github.com/zzjcool/pi-legion/releases
|
|
@@ -359,7 +359,7 @@ interface LegionEventBus {
|
|
|
359
359
|
| 项 | 默认 | 判定 |
|
|
360
360
|
|---|---|---|
|
|
361
361
|
| `maxDepth`(全树深度) | 4 | 硬顶 = `MAX_NESTED_PATH_ENTRIES`(血缘路径在 4 处饱和;settings/env 超 4 截断——告警在 M1-integration 的 launch 路径接入,M0 阶段截断静默可接受)。每进程生效上限 C:根 `C = min(env 运营者覆盖, settings.legion.maxDepth, 4)`;深度 d、上限 C 的父 launch 角色 R:先过闸门 `d+1 > C` → BUDGET_EXCEEDED;再经 `PI_SUBAGENT_MAX_DEPTH` 下发 `C子 = min(C, d + R, 4)`(R 缺省=不收紧;非法值按叶子 R=1 处理;语义 = 子树代数含自身,位置无关,只收紧不放宽)。不变量:C子 ≤ C父,且每进程 depth ≤ maxDepth |
|
|
362
|
-
| `maxChildrenPerNode`(单节点扇出) | 8 | 父 supervisor
|
|
362
|
+
| `maxChildrenPerNode`(单节点扇出) | 8 | 父 supervisor 查自己 **live** 子节点数(`starting`/`running`/`blocked`;`settled`/`failed`/`retired` 不占槽,session 可保留 resume) |
|
|
363
363
|
| `maxActiveNodes`(全树并发 running+starting) | 30 | **全树真闸门**(替代只当 hint 的 maxConcurrentAgents):launch 前一条 SQL,超限 BUDGET_EXCEEDED + `budget_refused` 事件 |
|
|
364
364
|
|
|
365
365
|
### 11.2 心跳与孤儿:postman 循环每次刷新直接子节点 `updated_at`。根 supervisor 每 60s 扫描:非终态 + `updated_at` 超 90s = `orphan_detected` 事件 + mail 通知 CEO。恢复 = resume 链(CEO 裁决,auto-resume 默认关)。孤儿判定可判定化:**非终态 ∧ 无活跃进程 ∧ 心跳超时**。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-legion",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Pi Legion v2: recursive centurion trees on headless Pi RPC; shared SQLite WAL legion.db. Four-stage planning/implementing/reviewing/verifying state machine, parent/child/sibling legion_mail, three-layer /legion UI, upward MR trees, squad resume.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/legion/budget.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DatabaseSync } from "./db.ts";
|
|
2
|
+
import { LIVE_CHILD_STATUSES } from "./nodes.ts";
|
|
2
3
|
import { withTransaction } from "./db.ts";
|
|
3
4
|
import { appendEvent } from "./events.ts";
|
|
4
5
|
|
|
@@ -64,14 +65,15 @@ export function checkLaunchBudget(
|
|
|
64
65
|
): BudgetDecision {
|
|
65
66
|
validateRequest(request);
|
|
66
67
|
const limits = normalizeLimits(options.limits);
|
|
68
|
+
const liveStatuses = LIVE_CHILD_STATUSES.map(() => "?").join(", ");
|
|
67
69
|
const row = db
|
|
68
70
|
.prepare(
|
|
69
71
|
`SELECT
|
|
70
|
-
(SELECT COUNT(*) FROM nodes WHERE parent_id IS ?) AS child_count,
|
|
72
|
+
(SELECT COUNT(*) FROM nodes WHERE parent_id IS ? AND status IN (${liveStatuses})) AS child_count,
|
|
71
73
|
(SELECT COUNT(*) FROM nodes WHERE status IN ('running', 'starting')) AS active_count,
|
|
72
74
|
CASE WHEN ? IS NULL THEN 1 ELSE EXISTS(SELECT 1 FROM nodes WHERE id = ?) END AS parent_exists`,
|
|
73
75
|
)
|
|
74
|
-
.get(request.parentId, request.parentId, request.parentId);
|
|
76
|
+
.get(request.parentId, ...LIVE_CHILD_STATUSES, request.parentId, request.parentId);
|
|
75
77
|
const counts = {
|
|
76
78
|
children: Number(row?.child_count ?? 0),
|
|
77
79
|
activeNodes: Number(row?.active_count ?? 0),
|
|
@@ -86,7 +88,7 @@ export function checkLaunchBudget(
|
|
|
86
88
|
if (counts.children >= limits.maxChildrenPerNode) {
|
|
87
89
|
return denied(
|
|
88
90
|
"max_children_per_node",
|
|
89
|
-
`parent already has ${counts.children} children; maxChildrenPerNode is ${limits.maxChildrenPerNode}`,
|
|
91
|
+
`parent already has ${counts.children} live children; maxChildrenPerNode is ${limits.maxChildrenPerNode}`,
|
|
90
92
|
base,
|
|
91
93
|
);
|
|
92
94
|
}
|
package/src/legion/nodes.ts
CHANGED
|
@@ -21,6 +21,13 @@ export const NON_TERMINAL_NODE_STATUSES = [
|
|
|
21
21
|
"retired",
|
|
22
22
|
] as const satisfies readonly NodeStatus[];
|
|
23
23
|
|
|
24
|
+
/** Statuses that occupy a maxChildrenPerNode concurrency slot (not retired/session-only). */
|
|
25
|
+
export const LIVE_CHILD_STATUSES = [
|
|
26
|
+
"starting",
|
|
27
|
+
"running",
|
|
28
|
+
"blocked",
|
|
29
|
+
] as const satisfies readonly NodeStatus[];
|
|
30
|
+
|
|
24
31
|
export interface LegionNode {
|
|
25
32
|
id: string;
|
|
26
33
|
parentId: string | null;
|
package/src/runs/orchestrator.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { RunStore } from "./store.ts";
|
|
|
18
18
|
import type { LegionEventBus, LegionProcessEventType } from "../extension/event-bus.ts";
|
|
19
19
|
import { appendEvent } from "../legion/events.ts";
|
|
20
20
|
import { withLaunchBudget, type LegionBudgetLimits } from "../legion/budget.ts";
|
|
21
|
-
import { getChildByName, getNode, insertNode, nodeIdForChild, sanitizeNodeSlug, type LegionNode, updateNode } from "../legion/nodes.ts";
|
|
21
|
+
import { getChildByName, getNode, insertNode, nodeIdForChild, sanitizeNodeSlug, type LegionNode, updateNode, LIVE_CHILD_STATUSES } from "../legion/nodes.ts";
|
|
22
22
|
import { upsertUsage } from "../legion/usage.ts";
|
|
23
23
|
import { evaluatePhaseTransition, isLegionPhase, isTerminalPhase } from "../legion/state-machine.ts";
|
|
24
24
|
import { CHILD_ACCEPTANCE_ROLE_ENV, CHILD_ROLE_ENV } from "../extension/child-guard.ts";
|
|
@@ -341,7 +341,8 @@ export class Orchestrator {
|
|
|
341
341
|
const depthCeiling = input.utility
|
|
342
342
|
? parent.depth + 1
|
|
343
343
|
: Math.min(legion.settings?.maxDepth ?? this.maxDepthValue, MAX_NESTED_PATH_ENTRIES);
|
|
344
|
-
const
|
|
344
|
+
const liveStatuses = LIVE_CHILD_STATUSES.map(() => "?").join(", ");
|
|
345
|
+
const childCount = Number(legion.db.prepare(`SELECT COUNT(*) AS count FROM nodes WHERE parent_id = ? AND status IN (${liveStatuses})`).get(legion.nodeId, ...LIVE_CHILD_STATUSES)?.count ?? 0);
|
|
345
346
|
const maxChildren = previous ? Math.max(legion.settings?.maxChildrenPerNode ?? 8, childCount + 1) : legion.settings?.maxChildrenPerNode;
|
|
346
347
|
const activeReservation = previous && (previous.status === "starting" || previous.status === "running") ? 1 : 0;
|
|
347
348
|
const limits: LegionBudgetLimits = { maxDepth: depthCeiling, maxChildrenPerNode: maxChildren, maxActiveNodes: (legion.settings?.maxActiveNodes ?? 30) + activeReservation };
|