@nowcrew/daemon 0.6.14 → 0.6.15
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 +6 -3
- package/dist/config.js +4 -2
- package/dist/execution-runner.js +4 -1
- package/dist/host-execution-coordinator.js +3 -3
- package/dist/prompt.js +3 -3
- package/dist/serve.js +9 -3
- package/dist/shared-execution-slots.js +17 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -371,9 +371,10 @@ CREW_RUNTIME_SAFE=1
|
|
|
371
371
|
CREW_EXECUTION_MAX_PROMPT_BYTES=256000
|
|
372
372
|
CREW_EXECUTION_MAX_TIMEOUT_MS=10800000
|
|
373
373
|
CREW_EXECUTION_MAX_EVENT_BYTES=64000
|
|
374
|
-
CREW_MAX_PARALLEL=
|
|
374
|
+
CREW_MAX_PARALLEL=10
|
|
375
|
+
CREW_SCHEDULED_MAX_PARALLEL=4
|
|
375
376
|
CREW_EXECUTION_MAX_QUEUED_PER_AGENT=32
|
|
376
|
-
CREW_EXECUTION_MAX_PARALLEL_TOTAL=
|
|
377
|
+
CREW_EXECUTION_MAX_PARALLEL_TOTAL=10
|
|
377
378
|
CREW_EXECUTION_MAX_QUEUED_TOTAL=128
|
|
378
379
|
CREW_EXECUTION_MAX_STARTING_TOTAL=1
|
|
379
380
|
CREW_EXECUTION_MAX_STARTING_PER_RUNTIME=1
|
|
@@ -381,7 +382,9 @@ CREW_EXECUTION_START_GAP_MS=3000
|
|
|
381
382
|
CREW_EXECUTION_STARTUP_TIMEOUT_MS=120000
|
|
382
383
|
```
|
|
383
384
|
|
|
384
|
-
`
|
|
385
|
+
`CREW_MAX_PARALLEL` limits normal executions per Agent. `CREW_SCHEDULED_MAX_PARALLEL` separately
|
|
386
|
+
limits scheduled executions per Agent. `MAX_PARALLEL_TOTAL` is the machine-wide process cap shared by
|
|
387
|
+
protocol-v1 and legacy work.
|
|
385
388
|
Runtime startup is a separate FIFO gate: by default only one Claude, Codex, or Kimi process may be
|
|
386
389
|
initializing at a time, and launches are spaced by three seconds. A process leaves the startup gate
|
|
387
390
|
only after its runtime-specific ready event; startup timeout cancels the owned process tree.
|
package/dist/config.js
CHANGED
|
@@ -10,9 +10,10 @@ export const DEFAULT_EXECUTION_LIMITS = Object.freeze({
|
|
|
10
10
|
maxPromptBytes: 256_000,
|
|
11
11
|
maxTimeoutMs: 3 * 60 * 60_000,
|
|
12
12
|
maxEventBytes: 64_000,
|
|
13
|
-
maxParallelPerAgent:
|
|
13
|
+
maxParallelPerAgent: 10,
|
|
14
|
+
maxParallelScheduledPerAgent: 4,
|
|
14
15
|
maxQueuedPerAgent: 32,
|
|
15
|
-
maxParallelTotal:
|
|
16
|
+
maxParallelTotal: 10,
|
|
16
17
|
maxQueuedTotal: 128,
|
|
17
18
|
maxStartingTotal: 1,
|
|
18
19
|
maxStartingPerRuntime: 1,
|
|
@@ -68,6 +69,7 @@ export function loadConfig(env = process.env) {
|
|
|
68
69
|
maxTimeoutMs: integerEnvAtMost(env, "CREW_EXECUTION_MAX_TIMEOUT_MS", DEFAULT_EXECUTION_LIMITS.maxTimeoutMs, MAX_EXECUTION_TIMEOUT_MS),
|
|
69
70
|
maxEventBytes: integerEnvAtLeast(env, "CREW_EXECUTION_MAX_EVENT_BYTES", DEFAULT_EXECUTION_LIMITS.maxEventBytes, MIN_EXECUTION_EVENT_BYTES),
|
|
70
71
|
maxParallelPerAgent: positiveIntegerEnv(env, "CREW_MAX_PARALLEL", DEFAULT_EXECUTION_LIMITS.maxParallelPerAgent),
|
|
72
|
+
maxParallelScheduledPerAgent: positiveIntegerEnv(env, "CREW_SCHEDULED_MAX_PARALLEL", DEFAULT_EXECUTION_LIMITS.maxParallelScheduledPerAgent),
|
|
71
73
|
maxQueuedPerAgent: positiveIntegerEnv(env, "CREW_EXECUTION_MAX_QUEUED_PER_AGENT", DEFAULT_EXECUTION_LIMITS.maxQueuedPerAgent),
|
|
72
74
|
maxParallelTotal: positiveIntegerEnv(env, "CREW_EXECUTION_MAX_PARALLEL_TOTAL", DEFAULT_EXECUTION_LIMITS.maxParallelTotal),
|
|
73
75
|
maxQueuedTotal: positiveIntegerEnv(env, "CREW_EXECUTION_MAX_QUEUED_TOTAL", DEFAULT_EXECUTION_LIMITS.maxQueuedTotal),
|
package/dist/execution-runner.js
CHANGED
|
@@ -231,7 +231,10 @@ function admission(spec, config, dependencies, at) {
|
|
|
231
231
|
|| dependencies.facts.queuedTotal < 0) {
|
|
232
232
|
return { rejected: rejection(spec.executionId, "invalid_spec", "Invalid local resource facts", at) };
|
|
233
233
|
}
|
|
234
|
-
const
|
|
234
|
+
const maxParallelForAgent = spec.context.scheduledRunId === undefined
|
|
235
|
+
? config.executionLimits.maxParallelPerAgent
|
|
236
|
+
: config.executionLimits.maxParallelScheduledPerAgent;
|
|
237
|
+
const agentAtCapacity = dependencies.facts.activeForAgent >= maxParallelForAgent;
|
|
235
238
|
const machineAtCapacity = dependencies.facts.activeTotal >= config.executionLimits.maxParallelTotal;
|
|
236
239
|
const slotInvalid = dependencies.slot?.state === "ready"
|
|
237
240
|
? agentAtCapacity || machineAtCapacity
|
|
@@ -4,7 +4,7 @@ import { mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { defaultProcessController } from "./execution-journal.js";
|
|
6
6
|
import { createJournalLease, createJournalLeaseRegistry, JournalLockedError, } from "./execution-journal-lock.js";
|
|
7
|
-
const HOST_EXECUTION_SLOT_COUNT =
|
|
7
|
+
const HOST_EXECUTION_SLOT_COUNT = 10;
|
|
8
8
|
const HOST_STARTUP_GAP_MS = 3_000;
|
|
9
9
|
const RETRY_MS = 50;
|
|
10
10
|
export function defaultHostExecutionCoordinatorRoot(userHome = homedir()) {
|
|
@@ -215,8 +215,8 @@ export function createHostExecutionCoordinator(options = {}) {
|
|
|
215
215
|
}
|
|
216
216
|
export function hostCoordinatedSlotManager(local, host) {
|
|
217
217
|
return {
|
|
218
|
-
reserve: (handle, kind) => {
|
|
219
|
-
const localReservation = local.reserve(handle, kind);
|
|
218
|
+
reserve: (handle, kind, executionClass) => {
|
|
219
|
+
const localReservation = local.reserve(handle, kind, executionClass);
|
|
220
220
|
if (!localReservation.accepted)
|
|
221
221
|
return localReservation;
|
|
222
222
|
const hostReservation = host.reserveExecution(localReservation.ready);
|
package/dist/prompt.js
CHANGED
|
@@ -234,14 +234,14 @@ ${taskAndScheduleCommands}`;
|
|
|
234
234
|
|
|
235
235
|
## 并行与记忆(CRITICAL — 防冲突 + 防上下文膨胀)
|
|
236
236
|
你**可能同时在处理多个任务**(你自己的多个并行进程)。为避免互相覆盖、并保持上下文精简,严格遵守:
|
|
237
|
-
- **普通任务默认只写两处**:① 本任务的隔离 cwd(代码/草稿);② 本任务的工作日志 **\`$CREW_TASK_LOG\`**(每任务一份,别人不会碰)。默认不要修改共享 \`$CREW_HOME/MEMORY.md\` 或 notes
|
|
238
|
-
-
|
|
237
|
+
- **普通任务默认只写两处**:① 本任务的隔离 cwd(代码/草稿);② 本任务的工作日志 **\`$CREW_TASK_LOG\`**(每任务一份,别人不会碰)。默认不要修改共享 \`$CREW_HOME/MEMORY.md\` 或 notes/;只有当前用户明确要求更新或记住长期记忆,或明确表达具体且跨后续任务持续生效的规则、偏好或约束时,才按下方条件写入相关共享文件。在工作日志里记:当前阶段 / 下一步 / 卡在谁 / 关键结论(每条尽量 ≤3 行)。
|
|
238
|
+
- **当前请求明确要求更新或记住长期记忆,明确表达需要跨后续任务持续生效的具体规则、偏好或约束,或当前消息是对本线程紧邻记忆方案的无歧义确认时**,可以按最小范围直接写共享记忆。例如“以后所有测试报告统一按五部分写”属于明确长期意图,即使没有使用“记忆”一词。先读取相关索引和 note,语义已存在时不重复追加,不要把 Agent 自己判断值得记住视为用户授权。
|
|
239
239
|
- 本节的直接更新共享记忆和相关 task 流转仅适用于用户消息触发的普通交互执行。memory_prune 和 scheduled 执行只遵守各自唤醒指令,不得依据本节认领、创建、重开或推进 task。
|
|
240
240
|
- 普通交互执行将写共享记忆时,使用当前线程唯一的普通 task:已有非终态 task 时先按归属规则确保当前 Agent 成功认领;当前线程没有 task 时先创建并认领一个普通 task;已有 task 为 done/closed 时不得由 Agent 重开,done 报告当前轻量方案无法安全复用终态 task,closed 保持人工终态。遇到终态或归属问题时报告冲突且不得创建第二个 task 或新的任务类型。完成写入后,只有任务本身的全部工作已完成时才推进终态以触发 prune。
|
|
241
241
|
- 提出共享记忆修改方案并等待用户确认时,该 task 仍有剩余工作,保持 in_review 或其它非终态;收到无歧义确认后再执行写入,并仅在全部工作完成后推进终态。
|
|
242
242
|
- 普通执行直接更新后,仍然保留任务结束后的专门收尾执行,不因已经写过而跳过 prune。
|
|
243
243
|
- 新建 note 必须说明现有分类为何不适用;MEMORY.md 默认只更新 Knowledge Index,不得默认修改 Role、Core Goals 或 Active Context;只有用户明确要求或任务本身就是档案/状态维护时例外。
|
|
244
|
-
-
|
|
244
|
+
- 仅对本次或当前任务生效的要求不持久化。未明确要求、记忆意图含糊或仅由 Agent 自己判断值得记住时,不直接写共享记忆,也不把含糊内容写成长期记忆候选。例如“以后注意一下”等缺少具体内容或适用范围的表达仍属于含糊意图。含糊表达本身不作为长期记忆候选,但不得因此跳过整个 task 的 memory_prune;任务按正常状态流程完成。收尾只能处理该任务其它符合现有收尾规则的稳定内容,不允许让收尾代替用户补充记忆授权。
|
|
245
245
|
- 若用户明确要求本次不要修改共享记忆,同样不写共享记忆或长期记忆候选。任务尚未完成时,把该禁止要求作为运行状态写入 $CREW_TASK_LOG;该运行状态不是长期记忆候选,后续执行必须保留该状态。若用户之后在同一任务中明确撤销禁止,移除该运行状态并按最新明确授权处理。完成 task 前检查该状态:Agent 可自行改状态时,使用 crew task update <taskId> --status done --skip-memory-prune。CLI 返回状态由 Server 管理时沿用上述 Server 管理任务的安全回退:不重试、不绕过权限;收尾会读取该运行状态并禁止共享记忆写入,但本路径不提供机器级 skip 保证。不要只在回复里声明不修改。
|
|
246
246
|
- **跨任务协调看实时清单**:用 \`crew task list --mine\` 查你当前所有任务及其状态(谁在排队、谁在评审)。例:你一次只能测一个页面时,据此判断"正在测 #1、#2 排队",在 #2 的工作日志里标注"等 #1 完成"。
|
|
247
247
|
- 需要更多背景时再 \`Read $CREW_HOME/notes/<topic>.md\`;不要一次把所有笔记读进上下文。
|
package/dist/serve.js
CHANGED
|
@@ -71,7 +71,8 @@ export function serve(config, opts = {}) {
|
|
|
71
71
|
let detectedExecutionRuntimes = [];
|
|
72
72
|
let runtimeFacts = null;
|
|
73
73
|
const runtimeProbe = createRuntimeProbeCoordinator();
|
|
74
|
-
const hostCoordinator = opts.execution?.hostCoordinator
|
|
74
|
+
const hostCoordinator = opts.execution?.hostCoordinator
|
|
75
|
+
?? createHostExecutionCoordinator({ executionSlots: config.executionLimits.maxParallelTotal });
|
|
75
76
|
const localSlots = createSharedSlotManager(config.executionLimits);
|
|
76
77
|
const sharedSlots = hostCoordinatedSlotManager(localSlots, hostCoordinator);
|
|
77
78
|
const runtimeStartupGate = hostCoordinatedStartupGate(createRuntimeStartupGate(config.executionLimits), hostCoordinator);
|
|
@@ -496,12 +497,14 @@ export function serve(config, opts = {}) {
|
|
|
496
497
|
task_key: spec.workspace.taskKey,
|
|
497
498
|
});
|
|
498
499
|
}
|
|
499
|
-
const
|
|
500
|
+
const executionClass = spec.context.scheduledRunId === undefined ? "normal" : "scheduled";
|
|
501
|
+
const reservation = sharedSlots.reserve(spec.agent.handle, "execution", executionClass);
|
|
500
502
|
if (!reservation.accepted) {
|
|
501
503
|
dslog("execution.machine_queue_rejected", "机器执行队列已满", {
|
|
502
504
|
level: "WARN",
|
|
503
505
|
execution_id: spec.executionId,
|
|
504
506
|
agent_handle: spec.agent.handle,
|
|
507
|
+
execution_class: executionClass,
|
|
505
508
|
...reservation.facts,
|
|
506
509
|
});
|
|
507
510
|
safeExecutionSend(ExecutionRejectedSchema.parse({
|
|
@@ -517,6 +520,7 @@ export function serve(config, opts = {}) {
|
|
|
517
520
|
dslog("execution.machine_queued", "execution 已进入机器队列", {
|
|
518
521
|
execution_id: spec.executionId,
|
|
519
522
|
agent_handle: spec.agent.handle,
|
|
523
|
+
execution_class: executionClass,
|
|
520
524
|
...reservation.facts,
|
|
521
525
|
});
|
|
522
526
|
}
|
|
@@ -534,6 +538,7 @@ export function serve(config, opts = {}) {
|
|
|
534
538
|
...(activeSameTask > 1 ? { level: "WARN" } : {}),
|
|
535
539
|
execution_id: spec.executionId,
|
|
536
540
|
agent_handle: spec.agent.handle,
|
|
541
|
+
execution_class: executionClass,
|
|
537
542
|
task_key: spec.workspace.taskKey,
|
|
538
543
|
active_same_task: activeSameTask,
|
|
539
544
|
...reservation.facts,
|
|
@@ -602,6 +607,7 @@ export function serve(config, opts = {}) {
|
|
|
602
607
|
dslog("execution.machine_slot_ready", "execution 获得机器执行名额", {
|
|
603
608
|
execution_id: spec.executionId,
|
|
604
609
|
agent_handle: spec.agent.handle,
|
|
610
|
+
execution_class: executionClass,
|
|
605
611
|
queue_ms: Date.now() - machineQueueEnteredAt,
|
|
606
612
|
active_total: snapshot.activeTotal,
|
|
607
613
|
queued_total: snapshot.queuedTotal,
|
|
@@ -824,7 +830,7 @@ export function serve(config, opts = {}) {
|
|
|
824
830
|
}
|
|
825
831
|
}
|
|
826
832
|
running.add(key);
|
|
827
|
-
legacyReservation = sharedSlots.reserve(msg.agentHandle, "legacy");
|
|
833
|
+
legacyReservation = sharedSlots.reserve(msg.agentHandle, "legacy", scheduled === null ? "normal" : "scheduled");
|
|
828
834
|
if (legacyReservation.isQueued()) {
|
|
829
835
|
const snapshot = sharedSlots.snapshot();
|
|
830
836
|
dslog("run.machine_queued", "legacy run 已进入机器队列", {
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
export function createSharedSlotManager(limits) {
|
|
2
|
-
const
|
|
2
|
+
const activeByHandleAndClass = new Map();
|
|
3
3
|
const queue = [];
|
|
4
4
|
let activeTotal = 0;
|
|
5
5
|
let exclusive = false;
|
|
6
6
|
const queuedFor = (handle) => queue.filter((entry) => !entry.released && entry.handle === handle).length;
|
|
7
|
+
const activeKey = (handle, executionClass) => `${handle}:${executionClass}`;
|
|
8
|
+
const maxParallelFor = (executionClass) => executionClass === "scheduled"
|
|
9
|
+
? limits.maxParallelScheduledPerAgent
|
|
10
|
+
: limits.maxParallelPerAgent;
|
|
7
11
|
const promote = () => {
|
|
8
12
|
while (activeTotal < limits.maxParallelTotal) {
|
|
9
13
|
const index = queue.findIndex((entry) => !entry.released
|
|
10
|
-
&& (
|
|
14
|
+
&& (activeByHandleAndClass.get(activeKey(entry.handle, entry.executionClass)) ?? 0)
|
|
15
|
+
< maxParallelFor(entry.executionClass));
|
|
11
16
|
if (index < 0)
|
|
12
17
|
return;
|
|
13
18
|
const [next] = queue.splice(index, 1);
|
|
@@ -15,13 +20,15 @@ export function createSharedSlotManager(limits) {
|
|
|
15
20
|
continue;
|
|
16
21
|
next.promoted = true;
|
|
17
22
|
activeTotal += 1;
|
|
18
|
-
|
|
23
|
+
const key = activeKey(next.handle, next.executionClass);
|
|
24
|
+
activeByHandleAndClass.set(key, (activeByHandleAndClass.get(key) ?? 0) + 1);
|
|
19
25
|
next.resolve();
|
|
20
26
|
}
|
|
21
27
|
};
|
|
22
28
|
return {
|
|
23
|
-
reserve: (handle, kind) => {
|
|
24
|
-
const
|
|
29
|
+
reserve: (handle, kind, executionClass = "normal") => {
|
|
30
|
+
const key = activeKey(handle, executionClass);
|
|
31
|
+
const activeForAgent = activeByHandleAndClass.get(key) ?? 0;
|
|
25
32
|
const queuedForAgent = queuedFor(handle);
|
|
26
33
|
const facts = {
|
|
27
34
|
activeForAgent,
|
|
@@ -39,7 +46,7 @@ export function createSharedSlotManager(limits) {
|
|
|
39
46
|
};
|
|
40
47
|
}
|
|
41
48
|
const canStartImmediately = activeTotal < limits.maxParallelTotal
|
|
42
|
-
&& activeForAgent <
|
|
49
|
+
&& activeForAgent < maxParallelFor(executionClass)
|
|
43
50
|
&& queue.length === 0;
|
|
44
51
|
if (kind === "execution" && !canStartImmediately && (queuedForAgent >= limits.maxQueuedPerAgent
|
|
45
52
|
|| queue.length >= limits.maxQueuedTotal)) {
|
|
@@ -55,6 +62,7 @@ export function createSharedSlotManager(limits) {
|
|
|
55
62
|
const ready = new Promise((resolve) => { resolveReady = resolve; });
|
|
56
63
|
const entry = {
|
|
57
64
|
handle,
|
|
65
|
+
executionClass,
|
|
58
66
|
released: false,
|
|
59
67
|
promoted: false,
|
|
60
68
|
resolve: resolveReady,
|
|
@@ -73,11 +81,11 @@ export function createSharedSlotManager(limits) {
|
|
|
73
81
|
entry.released = true;
|
|
74
82
|
if (entry.promoted) {
|
|
75
83
|
activeTotal = Math.max(0, activeTotal - 1);
|
|
76
|
-
const nextForAgent = Math.max(0, (
|
|
84
|
+
const nextForAgent = Math.max(0, (activeByHandleAndClass.get(key) ?? 1) - 1);
|
|
77
85
|
if (nextForAgent === 0)
|
|
78
|
-
|
|
86
|
+
activeByHandleAndClass.delete(key);
|
|
79
87
|
else
|
|
80
|
-
|
|
88
|
+
activeByHandleAndClass.set(key, nextForAgent);
|
|
81
89
|
}
|
|
82
90
|
else {
|
|
83
91
|
const index = queue.indexOf(entry);
|