#11350

codemode: long-running scripts in in-process subagents starve every other session (no per-session fairness, no holder info)

Opencad0popened 15d ago

What happened?

pi 1.4.0, Linux aarch64, default limit of 2 workers. One interactive session runs two in-process background subagents, and all three use codemode for every tool call. For about an hour, every codemode script in the parent session failed after its full 60 s wait:

No codemode worker became free within 60 seconds. All 2 workers, shared by every session in this process, are held by other scripts. Retry after they finish.

The script did not start.

Even a one-line grep failed this way, several times in a row. One subagent stopped its own work for the same reason. The parent could still reach the subagents through direct tools, but everything that needs bash, read or write goes through codemode, so it could do nothing. Nothing said which scripts held the workers, so there was no way to tell which subagent to steer or stop.

#11184 (fixed in 885a564b3) made the limit wait instead of failing, and PI_CODEMODE_MAX_WORKERS is the documented knob for in-process subagents (docs/environment-variables.md). That covers bursts. It does not cover long scripts. A worker keeps its slot from launch until exit (packages/codemode/src/runtime/host.ts:30-38), including while the script awaits a host tool such as a long bash. I understand from #10748 that this is intentional. Waiters sit in one FIFO with no session key (execute.ts:72-105). So any N long scripts from subagents hold every slot, whatever N is set to, and every other session in the process is locked out until they finish.

What I'd like

  1. Per-session fairness at admission. For example, a session that already holds a worker doesn't get the next free one while a session holding none is waiting, or one slot is kept back for sessions that hold none. With that, the interactive session always gets a worker within one script's run.
  2. Say who holds the workers. The error at execute.ts:567 gives only counts. Listing each holder's session id or label, its age, and the tool it is waiting on would let the user or the model stop or steer the holder instead of retrying blind.

Steps to reproduce

Against 1.4.0's own executeCodemode, with a host tool that sleeps:

import { executeCodemode } from ".../pi-coding-agent/dist/extensions/codemode/execute.js";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const ctx = {
  tools: [{ name: "slow", description: "Waits N seconds", parameters: { type: "object", properties: { seconds: { type: "number" } }, required: ["seconds"] } }],
  executeTool: async (_n, a) => { await sleep(a.seconds * 1000); return { toolCall: { id: "c" + Math.random() }, result: { content: [{ type: "text", text: "ok" }], details: {} }, isError: false }; },
  sessionManager: { getBranch: () => [] }, modelRegistry: {},
};
const run = (id, code) => executeCodemode(id, { code }, undefined, undefined, ctx, {});
run("A", "return await tools.slow({ seconds: 130 })");
run("B", "return await tools.slow({ seconds: 130 })");
await sleep(2000);  run("C", "return 1");   // fails at 62 s
await sleep(61000); run("D", "return 2");   // fails at 123 s
await sleep(70000); run("E", "return 3");   // runs at 133 s, after A and B exit

Observed (PI_CODEMODE_MAX_WORKERS unset):

  2.0s C submitted
 62.0s C isError=true  No codemode worker became free within 60 seconds. ...
 63.0s D submitted
123.0s D isError=true  No codemode worker became free within 60 seconds. ...
130.1s A, B completed (host tool finished)
133.0s E completed in 0.0 s

The repro shares one context for all scripts, so it shows the pool behaviour, not separate sessions. In the real case, A and B were two in-process subagents and C, D and E were the parent session.

Environment

  • pi-coding-agent 1.4.0
  • Linux aarch64
  • Subagents run in-process (pi-subagents)

Activity

0

No activity yet.