Files
makelore/.project-docs/30-worklog/tasks/20260823-pi-child-workers-5c8e2a71.md

13 KiB

Task: Implement PI-080 child workers and subagent runtime

Identity

  • Task ID: 20260823-pi-child-workers-5c8e2a71
  • Mode: Feature
  • Branch: codex/20260823-pi-child-workers-5c8e2a71-pi-child-workers
  • Worktree: D:\Datas\OthersProjects\makelore-pi-child-workers-5c8e2a71
  • Base commit: b806c78139
  • Owner: codex
  • Status: Ready for integration

Scope

  • Implement PI-080 — Subagent scheduler and nested protocol on top of the cumulative PI-070 baseline b806c78139aa11e338c680d4c3fa92e076901aa2.
  • Own the Main-private child scheduler/supervisor, managed ephemeral child Pi opener, parent-child lifecycle registry, global child concurrency limit, and strict subagent.v1 validation/projection.
  • Extend the single managed Makelore Pi extension and its authenticated Main bridge only as needed to dispatch single, parallel, and chain child work and stream stable product details back to the parent tool.
  • Add fake scheduler integration coverage, real Pi child process smoke, process/semaphore leak coverage, bridge identity/abort coverage, and same-project parent/child write-lease coverage. Renderer/Host product routes and final nested UI remain owned by later tickets.

Intent And Constraints

  • Follow planner task 20260822-pi-runtime-spec-b6e2c9a4, ticket PI-080, Spec UX-041 through UX-044, EXT-001 through EXT-006, and section 9.4.
  • A dispatch accepts exactly one mode, contains at most eight bounded tasks, and resolves only through Makelore-owned subagent.v1; unknown schema or raw Pi/example details never enter product state or diagnostics.
  • Every child owns an independent ephemeral Pi process/context and uses an enabled project Agent's managed prompt, skills, model, credentials, and one exact read-only or coding tool profile. Child sessions are not persisted as recoverable user Conversations and children cannot recursively dispatch more subagents.
  • Reuse the same application PiProcessBudget as parent workers for the locked parent/child soft cap of eight and enforce one global FIFO maximum of four running children across all parents. Do not add a competing process counter.
  • Reuse PI-070's single project mutation lease. A coding child loads the same managed extension lease hooks as its parent; read-only children receive no mutation tools. Do not introduce child worktrees or a second mutation lock.
  • Parent abort/crash/recover/dispose, stale generation/run identity, bridge disconnect, and host shutdown cancel unfinished children and release every child/process permit. Parallel failures preserve sibling results; chain failure marks the remaining tasks skipped.
  • Keep Provider credentials in the child environment/redaction set only. Real external Provider validation remains Explicitly Waived / Accepted Risk with realTurnVerified=false; macOS x64/arm64 remains deferred to mandatory PI-150. Neither is a Pass.
  • Keep the change surgical: no dual runtime, generic plugin/permission system, project/user Pi discovery, Renderer wire changes, Host product routes, OpenCode refactor, or canonical project-memory edits.

Outcome

  • Added the Main-private PiSubagentScheduler with strict one-to-eight task validation, single/parallel/chain execution, one FIFO four-child semaphore, a parent/run dispatch registry, shared PiProcessBudget acquisition, idle parent reclamation when the eight-process budget is full, and deterministic abort/error/skipped projection without raw child errors.
  • Planner review of f1c7cd8 found that a previously queued parent process budget waiter could take a reclaimed idle permit before the child. The corrected budget keeps normal waiters FIFO, keeps child waiters FIFO, and prioritizes child reservations so a running parent cannot deadlock while waiting on its own child. The child enters the priority queue before idle reclamation releases capacity.
  • Planner re-review of 3de85d6 confirmed the original three findings closed, then reproduced two further supported failure paths: a child reservation could wait forever when no parent was idle during the one-time reclaim but a spawning/running parent became reclaimable later; and reclaim/worker-stop failure could leave an unowned process lease. The pool now wakes pending reclaimers on ready/idle state transitions without polling. Scheduler reservations race direct capacity against this cancellable event-driven reclaim and always settle/release on reclaim rejection, parent abort, or any other early exit. Parent stop() now releases its process lease in finally.
  • Planner re-review of a10b98e confirmed both lease-lifecycle findings closed, then reproduced the production-capacity shape 4 running + 4 queued: queued parent workers retained all eight process leases, leaving no ready/idle worker for four children to reclaim. The pool now prefers ready/idle eviction, then suspends the oldest queued parent process while retaining its top-level FIFO entry, Conversation state, and session binding. When its turn arrives, the same queue entry reopens that session as a new worker generation before sending the original prompt. This closes the deadlock without dropping queued work, reordering it, adding another queue, or exceeding the shared eight-process cap.
  • Planner re-review of 1727b75 confirmed the 4+4 deadlock closed, then reproduced a supported stop/dispatch overlap: a queued worker could be selected while its suspension stop() was still pending, so ensureFresh saw the not-yet-released old lease and sent the prompt to a stopping generation. ensureFresh now awaits any existing processStopFlight, rechecks record ownership/rebuild state, and only then reopens a lease-less worker. The old generation never receives the queued prompt.
  • Final planner review of 30c8bfd passed with zero Standards findings and zero Spec findings. Independent reproduction confirmed the exact stop/slot overlap and the production-default maxRunning=4, maxIdle=4, maxProcesses=8 4+4 path; PI-080 is Done and the ready frontier advances to PI-090 and PI-100.
  • Added the managed ephemeral child opener. It resolves only enabled, unarchived project Agents from .niancode/project.json, materializes their exact model/prompt/skills, keeps credentials in the child environment and redaction set, selects the exact read-only or coding tools, uses --no-session, and returns bounded public summary/usage data.
  • Upgraded the single materialized Makelore extension bundle to v2. Parent workers expose ask_user and subagent; child workers expose neither and retain only the shared mutation-lease hooks required by coding tools. The authenticated loopback bridge streams only NDJSON subagent.v1 details and rejects recursive child dispatch and stale identities.
  • Planner review of f1c7cd8 also found that Pi 0.84.2 treats --tools as a strict whitelist across built-in and extension tools. The managed parent default now explicitly includes subagent; child read-only/coding lists remain exact and exclude both subagent and ask_user.
  • Connected subagent dispatches to PI-070 generation resource cancellation so parent abort/crash/recover/dispose and bridge disconnect stop unfinished children and release child/process permits. Coding children join the same project write lease as their parent.
  • Added vendor-neutral strict subagent.v1 projection for live events, durable session hydration, and reducer validation. Unknown versions render one fixed unavailable message and never copy their raw payload into product state.
  • PiConversationRuntime now configures the scheduler/bridge against the existing pool generation tracker. Final product cutover and nested Renderer presentation remain outside this ticket.

Verification

  • Focused scheduler, managed-child, bridge, bundle, projector, session, process and contract tests passed, including: 1/4/8 task coverage and ninth-task rejection; two-parent global child concurrency of four; full-budget idle parent reclamation; parallel sibling preservation; chain skip/abort rules; parent generation cancellation with no orphan and zero leaked permits; recursive child rejection; parent/child same-project write-lease queuing; unknown schema/version raw-payload suppression; first reclaim finding no idle followed by delayed parent readiness and automatic child execution; reclaim rejection/abort with zero queued lease; and stop rejection with zero active/waiting lease. A production-scale regression also covers four running plus four queued parents dispatching four parallel children: all four queued workers suspend, all children complete, and the queued parents then resume FIFO with unchanged session bindings and generation 2. Their stop() calls are deliberately held behind a gate while an earlier parent settles; the test confirms no prompt reaches the stopping generation before the gate and the original prompt reaches only the reopened generation.
  • Locked real Pi 0.84.2 workspace smoke passed for both the parent worker and an ephemeral read-only child launched through Electron Node with the child extension role and --no-session. A probe extension reads Pi's actual active-tool list at session_start: the parent includes subagent, while the child is exactly read,grep,find,ls.
  • pnpm run test:pi-subagent:packaged passed 3/3. The dedicated runner uses PI-030's production bundler to create and validate a temporary staged production closure/manifest, then starts the staged Pi CLI as an ephemeral read-only child, verifies get_state, the exact active tools and child extension role, and clean stdin shutdown. The normal suite skips only this staging case so it does not perform a production npm ci on every unit run.
  • The real materialized extension bundle test executes subagent through the authenticated Main bridge; the active-tool process probes and bridge execute test jointly cover visibility and tool invocation without an external Provider.
  • All cumulative Pi tests passed: 22 files, 111 passed and 1 staged-only skipped; the staged-only command passed separately as described above.
  • pnpm run typecheck: passed.
  • pnpm run lint:check: passed with 0 errors and 6 pre-existing frontend warnings outside this task.
  • pnpm run build:vite: passed for Renderer, Main, Preload, and utility worker bundles; existing chunk-size/dynamic-import warnings remain.
  • The final full-suite first pass hit the known Windows temporary JSON rename EPERM in the unchanged conversation store while the other 2213 tests passed. The failing runtime test passed 1/1 in isolation and the single full rerun passed: 202 files, 2214 passed and 1 staged-only skipped.
  • Real external Provider validation remains Explicitly Waived / Accepted Risk with realTurnVerified=false. Provider concurrency, credential isolation, protocol compatibility, and image-path risk are accepted rather than marked Pass. macOS x64/arm64 validation was not run and remains deferred to mandatory PI-150; it is not marked Pass.

Follow-ups

  • PI-130 owns the final nested Renderer execution graph and user-facing child error/abort presentation.
  • PI-150 owns final packaged provider-shaped subagent smoke and mandatory macOS x64/arm64 validation.

Promotion Candidates

  • Target canonical document: Pi runtime process/concurrency architecture. Proposal: record that child budget acquisition must reclaim an idle parent worker when the shared eight-process budget is full; otherwise the supported shape of four running parents plus four warm-idle parents can deadlock while the running parents wait on children. Evidence: the focused full-budget scheduler regression test and the shared-budget implementation in PI-080. Future impact: any final Main composition must pass one PiProcessBudget to both the parent pool and child scheduler, preserve child-priority budget reservations ahead of normal parent-start waiters, and wire the scheduler's cancellable capacity reclaimer to PiWorkerPool.reclaimIdleWorker. The pool must notify an already waiting child when a spawning/running parent becomes ready/idle, and stop/reclaim failure must never retain a lease. If the cap consists only of running and queued parents, it must suspend queued parent processes in FIFO-safe/LRU order, retain their queue entries and session bindings, and reopen them as a new generation when scheduled. A queued run selected while suspension is still stopping must await that stop flight and recheck ownership before it can reopen or send the prompt. Semantic conflicts: none with the accepted PI runtime specification; this makes its parent/child cap executable. Human confirmation required: no, unless integration changes the accepted process-cap policy.