@youngjurry/pi-agents 0.7.1 → 0.7.2
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 +7 -0
- package/control.ts +35 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.2 - 2026-09-02
|
|
4
|
+
|
|
5
|
+
- Keep execution slots reserved until an `AgentSession` is fully settled and safe to evict.
|
|
6
|
+
- Treat temporary resident saturation as queue backpressure instead of permanently failing waiting agents.
|
|
7
|
+
- Resume blocked FIFO scheduling on settlement without busy-looping or losing wake-up signals.
|
|
8
|
+
- Add regression coverage for the batch overflow race that previously produced `agent residency limit reached` errors.
|
|
9
|
+
|
|
3
10
|
## 0.7.1 - 2026-09-01
|
|
4
11
|
|
|
5
12
|
- Order the `/agents` picker by latest task assignment, newest first, instead of alphabetically.
|
package/control.ts
CHANGED
|
@@ -163,6 +163,7 @@ export class AgentControl {
|
|
|
163
163
|
private modelRuntimePromise?: Promise<ModelRuntime>;
|
|
164
164
|
private activeExecutionSlots = 0;
|
|
165
165
|
private schedulerPromise?: Promise<void>;
|
|
166
|
+
private schedulerRerunRequested = false;
|
|
166
167
|
private spawnOperationTail: Promise<void> = Promise.resolve();
|
|
167
168
|
private disposed = false;
|
|
168
169
|
private shuttingDown = false;
|
|
@@ -603,15 +604,22 @@ export class AgentControl {
|
|
|
603
604
|
return loader;
|
|
604
605
|
}
|
|
605
606
|
|
|
606
|
-
private
|
|
607
|
+
private tryEvictForResidency(protectedPath?: string): boolean {
|
|
607
608
|
const allResidents = [...this.agentsByPath.values()].filter((record) => record.loaded);
|
|
608
|
-
if (allResidents.length < this.maxResidentSubagents) return;
|
|
609
|
+
if (allResidents.length < this.maxResidentSubagents) return true;
|
|
609
610
|
const candidate = allResidents
|
|
610
611
|
.filter((record) => record.path !== protectedPath)
|
|
611
612
|
.filter((record) => record.status !== "running" && !record.holdsExecutionSlot && record.session?.isIdle !== false)
|
|
612
613
|
.sort((left, right) => left.lastUsedAt - right.lastUsedAt)[0];
|
|
613
|
-
if (!candidate)
|
|
614
|
+
if (!candidate) return false;
|
|
614
615
|
this.unload(candidate);
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
private async evictForResidency(protectedPath?: string): Promise<void> {
|
|
620
|
+
if (!this.tryEvictForResidency(protectedPath)) {
|
|
621
|
+
throw new Error(`agent residency limit reached (${this.maxResidentSubagents}); all resident agents are busy`);
|
|
622
|
+
}
|
|
615
623
|
}
|
|
616
624
|
|
|
617
625
|
private unload(record: AgentRecord): void {
|
|
@@ -656,6 +664,13 @@ export class AgentControl {
|
|
|
656
664
|
this.noteTurnEnd(record.path, lastAssistant?.role === "assistant" ? lastAssistant.stopReason : undefined);
|
|
657
665
|
}
|
|
658
666
|
}
|
|
667
|
+
if (event.type === "agent_settled") {
|
|
668
|
+
this.releaseExecutionSlot(record);
|
|
669
|
+
this.persistState();
|
|
670
|
+
// Let AgentSession finish emitting its settled event before an LRU eviction
|
|
671
|
+
// can dispose this now-idle session.
|
|
672
|
+
queueMicrotask(() => void this.scheduleQueued());
|
|
673
|
+
}
|
|
659
674
|
this.changed();
|
|
660
675
|
});
|
|
661
676
|
}
|
|
@@ -686,10 +701,8 @@ export class AgentControl {
|
|
|
686
701
|
if (answer.aborted) {
|
|
687
702
|
record.status = "interrupted";
|
|
688
703
|
record.statusMessage = "interrupted";
|
|
689
|
-
this.releaseExecutionSlot(record);
|
|
690
704
|
this.persistState();
|
|
691
705
|
this.changed();
|
|
692
|
-
void this.scheduleQueued();
|
|
693
706
|
return;
|
|
694
707
|
}
|
|
695
708
|
if (answer.error) {
|
|
@@ -702,10 +715,8 @@ export class AgentControl {
|
|
|
702
715
|
record.statusMessage = undefined;
|
|
703
716
|
}
|
|
704
717
|
this.writeAgentResult(record);
|
|
705
|
-
this.releaseExecutionSlot(record);
|
|
706
718
|
this.persistState();
|
|
707
719
|
this.changed();
|
|
708
|
-
void this.scheduleQueued();
|
|
709
720
|
void this.deliver(record.path, record.parentPath, this.completionNotice(record), false, "AGENT_STATUS").catch(() => {});
|
|
710
721
|
}
|
|
711
722
|
|
|
@@ -862,8 +873,11 @@ export class AgentControl {
|
|
|
862
873
|
return records;
|
|
863
874
|
}
|
|
864
875
|
|
|
865
|
-
private async startQueued(record: AgentRecord): Promise<
|
|
866
|
-
if (record.status !== "queued") return;
|
|
876
|
+
private async startQueued(record: AgentRecord): Promise<boolean> {
|
|
877
|
+
if (record.status !== "queued") return true;
|
|
878
|
+
// A completed AgentSession emits agent_end just before it becomes idle. Treat a
|
|
879
|
+
// temporarily full resident set as backpressure, not as a permanent task error.
|
|
880
|
+
if (!record.session && !this.tryEvictForResidency(record.path)) return false;
|
|
867
881
|
this.reserveExecutionSlot();
|
|
868
882
|
record.holdsExecutionSlot = true;
|
|
869
883
|
record.status = "pending_init";
|
|
@@ -877,7 +891,7 @@ export class AgentControl {
|
|
|
877
891
|
record.statusMessage = "waiting for an execution slot";
|
|
878
892
|
this.releaseExecutionSlot(record);
|
|
879
893
|
this.unload(record);
|
|
880
|
-
return;
|
|
894
|
+
return true;
|
|
881
895
|
}
|
|
882
896
|
const content = [...(record.queuedMail ?? []), record.queuedMessage].filter((item): item is string => Boolean(item)).join("\n\n");
|
|
883
897
|
if ((record.queuedMail?.length ?? 0) > 0) this.mailboxPending.set(record.path, 0);
|
|
@@ -885,6 +899,7 @@ export class AgentControl {
|
|
|
885
899
|
record.queuedMail = undefined;
|
|
886
900
|
this.launch(record, content);
|
|
887
901
|
this.persistState();
|
|
902
|
+
return true;
|
|
888
903
|
} catch (error) {
|
|
889
904
|
record.status = "errored";
|
|
890
905
|
record.statusMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -896,6 +911,7 @@ export class AgentControl {
|
|
|
896
911
|
this.writeAgentResult(record);
|
|
897
912
|
this.persistState();
|
|
898
913
|
void this.deliver(record.path, record.parentPath, this.completionNotice(record), false, "AGENT_STATUS").catch(() => {});
|
|
914
|
+
return true;
|
|
899
915
|
}
|
|
900
916
|
}
|
|
901
917
|
|
|
@@ -903,20 +919,26 @@ export class AgentControl {
|
|
|
903
919
|
while (!this.disposed && !this.shuttingDown && this.activeExecutionSlots < this.maxConcurrentSubagents) {
|
|
904
920
|
const next = this.queuedRecords()[0];
|
|
905
921
|
if (!next) break;
|
|
906
|
-
await this.startQueued(next);
|
|
922
|
+
if (!(await this.startQueued(next))) break;
|
|
907
923
|
}
|
|
908
924
|
}
|
|
909
925
|
|
|
910
926
|
private async scheduleQueued(): Promise<void> {
|
|
911
927
|
if (this.disposed || this.shuttingDown) return;
|
|
912
|
-
if (this.schedulerPromise)
|
|
928
|
+
if (this.schedulerPromise) {
|
|
929
|
+
this.schedulerRerunRequested = true;
|
|
930
|
+
return this.schedulerPromise;
|
|
931
|
+
}
|
|
932
|
+
this.schedulerRerunRequested = false;
|
|
913
933
|
const operation = this.runQueuedScheduler();
|
|
914
934
|
this.schedulerPromise = operation;
|
|
915
935
|
try {
|
|
916
936
|
await operation;
|
|
917
937
|
} finally {
|
|
938
|
+
const rerun = this.schedulerRerunRequested;
|
|
939
|
+
this.schedulerRerunRequested = false;
|
|
918
940
|
if (this.schedulerPromise === operation) this.schedulerPromise = undefined;
|
|
919
|
-
if (!this.disposed && !this.shuttingDown
|
|
941
|
+
if (rerun && !this.disposed && !this.shuttingDown) {
|
|
920
942
|
queueMicrotask(() => void this.scheduleQueued());
|
|
921
943
|
}
|
|
922
944
|
}
|