Skip to content

fix(pi): keep panes working while async subagents run - #21882

Merged
nwparker merged 4 commits into
mainfrom
fix/18031-pi-lifecycle
Sep 21, 2026
Merged

nwparker merged 4 commits into
mainfrom
fix/18031-pi-lifecycle

Conversation

@nwparker

@nwparker nwparker commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​47 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​47
Prod 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​43 $\color{#cf222e}{\Huge{\mathbf{−}}}$​5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​38

What this fixes

Fixes #18031. Pi can launch a background pi-subagents task and then report the lead turn as settled. Orca used that signal to mark the pane done even though the child was still running.

The generated Pi/OMP extension now listens to the current lifecycle channel plus the documented async aliases, keeps a shared child roster across in-process reloads, and defers agent_end until all children complete, fail, or abort. Session boundaries clear the roster.

ELI5

The parent task is a teacher who asks helpers to finish homework. Orca used to mark the teacher done as soon as the teacher stopped talking. Now it waits until every helper says they are finished too.

Proof and testing

  • Focused lifecycle suite: 13 passed.
  • Extension source suite: 43 passed.
  • pnpm tc passed locally.
  • pnpm run check:code-quality:changed passed locally.
  • GitHub runtime verification passed on macOS, Ubuntu, and Windows.
  • GitHub typecheck, static analysis, package, and test shards passed on the prior commit; CI is rerunning for this follow-up.

Visual proof

N/A: this changes background agent status delivery and has no rendered UI surface. The proof is the generated-extension harness plus cross-platform runtime CI.

Review checklist

  • No user-facing negative trade-off identified.
  • No new wire fields or provider protocol changes.
  • Reload, malformed event, alias, and completion paths are covered.
  • ELI5 included.
  • Live authenticated Pi model session: unavailable on the validation hosts; CI runtime verification covers the generated extension contract.

AI disclosure

Prepared with Codex; implementation and verification were reviewed against the current Pi/OMP extension API and repository checks.

Related prior work: #19722 and #18032. Brennan Benson's provider-neutral descendant-roster work informed the design; this PR adapts it to the current Pi event contract.

@coderabbitai

coderabbitai Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Understand this PR’s impact

Explore downstream dependencies and potential security impact with Blast Radius.

View blast radius →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: b1c3c743-0a78-4948-bc87-c282e67ae799

📥 Commits

Reviewing files that changed from the base of the PR and between 0c3da6c and f671b7a.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The agent status handler now stores shared subagent lifecycle state on the Pi event bus. It registers lifecycle listeners once across extension reloads and supports asynchronous lifecycle aliases. It defers agent_end while subagents remain active, then posts it after completion. Session shutdown and session switching clear this state. The test harness now supplies a shared event emitter and exposes emitPiEvent. Tests cover malformed and unknown events, reloads, and lifecycle aliases.

Priority: ⬇️ Low

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly describes the main change: keeping panes active while asynchronous subagents run.
Description check ✅ Passed The description explains the problem, solution, testing, visual proof, linked issue, and AI use. It is sufficiently complete despite using custom section headings instead of the full template.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 90bf7676-6d49-4c49-af57-649265e518e0

📥 Commits

Reviewing files that changed from the base of the PR and between e476193 and 379eea2.

📒 Files selected for processing (3)
  • src/main/pi/agent-status-extension-omp-lifecycle.test.ts
  • src/main/pi/agent-status-extension-test-harness.ts
  • src/main/pi/agent-status-handler-source.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment on lines +148 to +150
' if (activePiSubagents.size === 0 && agentEndWaitingForSubagents) {',
' agentEndWaitingForSubagents = false',
' postAgentEndOnce()',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '1,290p' src/main/pi/agent-status-handler-source.ts
rg -n "agentEndWaitingForSubagents|postAgentEndOnce|agent_start|agent_settled|runGeneration|generation" src/main/pi/agent-status-handler-source.ts src/main/pi/*.test.ts

Repository: stablyai/orca

Length of output: 34296


🏁 Script executed:

sed -n '285,320p' src/main/pi/agent-status-handler-source.ts
sed -n '1,90p' src/main/pi/agent-status-extension-nested-run-settlement.test.ts

Repository: stablyai/orca

Length of output: 4592


Bind deferred completion to its run generation.

agentEndWaitingForSubagents does not identify the run that deferred completion. If a later agent_start occurs before the earlier run's child reaches a terminal lifecycle event, the child can empty activePiSubagents and call postAgentEndOnce() while the later run is active. Store the deferred endedRunGeneration, clear or replace it when a later run starts, and release agent_end only when that generation is still the current runGeneration.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The event this subscribes to is OMP's built-in task-tool channel, while #18031's environment (upstream Pi + pi-subagents) documents subagent:async-started / subagent:async-complete — so on the reported runtime the listener may never fire. Separately, the deferral's release path is not run-scoped, so it can post a stale agent_end during a later run or leave a pane stuck working. Both are detailed inline.

Reviewed changes

  • Pi subagent lifecycle listener — the generated extension now subscribes to task:subagent:lifecycle on pi.events, adds a started child to activePiSubagents, and posts agent_start.
  • Deferred completion — postAgentEndOnce returns early (setting agentEndWaitingForSubagents) while children are live; the terminal-status branch releases the deferred agent_end once the set empties. Session shutdown/switch clear both.
  • Harness support — the VM harness exposes a shared EventEmitter and an emitPiEvent helper; two new tests cover the happy path and malformed/unknown events.

ℹ️ Nitpicks

  • The pi.events.on subscription is never removed. On an in-process reload (which harness.reload() models) each factory invocation adds another listener while the old closure keeps its own activePiSubagents / generation state, so children started before the reload are invisible to the new closure and both closures react to later events. Consider unsubscribing, or note why the bus is per-invocation.
  • The two new tests only cover started-before-settle with a single child. None exercise a new lead run before the child terminates, a started arriving after the lead settled, or a reload — the three cases above.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

' const activePiSubagents = new Set<string>()',
' let agentEndWaitingForSubagents = false',
' const piEventBus = (pi as { events?: { on?: (name: string, handler: (event: unknown) => void) => void } }).events',
" piEventBus?.on?.('task:subagent:lifecycle', (event: unknown) => {",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The event subscribed here is emitted by OMP's built-in task tool (TASK_SUBAGENT_LIFECYCLE_CHANNEL = 'task:subagent:lifecycle' in oh-my-pi), not by pi-subagents, whose documented async events are subagent:async-started / subagent:async-complete. Issue #18031 reports upstream Pi 0.84.4 + pi-subagents 0.62.0, so on that runtime this listener never fires and the pane still settles early. The added tests cannot catch this — they drive a synthetic EventEmitter with the OMP channel name.

Technical details
# Event contract vs. reported environment

## Affected sites
- `src/main/pi/agent-status-handler-source.ts:136` — subscribes only to `task:subagent:lifecycle`.
- `src/main/pi/agent-status-extension-omp-lifecycle.test.ts:22,27` — emits that channel from a synthetic `EventEmitter`, so the test passes regardless of the real wire name.

## Evidence
- OMP (`can1357/oh-my-pi`) defines `TASK_SUBAGENT_LIFECYCLE_CHANNEL = "task:subagent:lifecycle"` and emits it from its built-in `task` executor:
  - https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/tui/src/overlays/session-observer-registry.ts
  - https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/task/executor.ts
- `pi-subagents` documents its async events as `subagent:async-started` / `subagent:async-complete` (section "Events"):
  - https://raw.githubusercontent.com/nicobailon/pi-subagents/main/docs/observability.md
- Upstream Pi ships no built-in subagents, so it has no `task:subagent:lifecycle` emitter.
- Issue #18031 environment: "Pi 0.84.4, pi-subagents 0.62.0", and the reporter states the process bus emits `subagent:async-started` / `subagent:async-complete`.

## Required outcome
- Either confirm this PR intentionally targets OMP's built-in `task` tool (and adjust the title/body, since it does not fix #18031 for upstream Pi), or also subscribe to the `pi-subagents` channels.
- Add a test that exercises the channel the targeted runtime actually emits, or cite a captured event log — the current harness cannot prove the name.

Comment on lines +146 to +151
" if (status !== 'completed' && status !== 'failed' && status !== 'aborted') return",
' activePiSubagents.delete(id)',
' if (activePiSubagents.size === 0 && agentEndWaitingForSubagents) {',
' agentEndWaitingForSubagents = false',
' postAgentEndOnce()',
' }',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The start and release paths are asymmetric: started always posts agent_start, but the release only calls postAgentEndOnce() when agentEndWaitingForSubagents is already true — and that flag is a bare boolean with no run identity. A child whose started lands after the lead's agent_end already posted (flag false) never releases, leaving the pane stuck working; conversely, a deferred run-1 agent_end can fire during a later run because postAgentEndOnce never checks that the ended run is still current.

Technical details
# Deferral is not run-scoped

## Affected sites
- `src/main/pi/agent-status-handler-source.ts:148` — release gated on `agentEndWaitingForSubagents`.
- `src/main/pi/agent-status-handler-source.ts:257-260` — `postAgentEndOnce` defers but does not require `endedRunGeneration === runGeneration`.
- `src/main/pi/agent-status-handler-source.ts:179-187` — `agent_start` bumps `runGeneration` but never resets the flag.

## Reachable sequences
Stuck working (start after end):
1. run 1 `agent_start` -> `agent_end` / `agent_settled` -> `postAgentEndOnce` posts `agent_end` (set empty, flag false).
2. child `started` -> `activePiSubagents = {child}`, `post('agent_start')`.
3. child `completed` -> set empty, but flag false -> `postAgentEndOnce` is never called. Pane stays working until some later lead turn.

Premature end during a later run:
1. run 1 `agent_start`, child `started`, run 1 `agent_end` / `agent_settled` -> deferred (`agentEndWaitingForSubagents = true`).
2. new lead `agent_start` -> `runGeneration` bumps; the flag is not reset.
3. child `completed` -> flag true -> `postAgentEndOnce`; `completionPostedGeneration !== endedRunGeneration`, so it posts run 1's `agent_end` while run 2 is working.

## Required outcome
- Every `agent_start` posted for a child must have a matching release, and a deferred completion must not settle a run that is no longer current.
- Guard the release on `endedRunGeneration === runGeneration` (the generation machinery this file documents at lines 234-239), and/or track the deferral per run instead of a bare boolean.
- Add coverage for: a new lead run before the child terminates; a `started` arriving after the lead settled.

## Open questions for the human
- Does Pi/OMP guarantee `started` strictly precedes the lead's terminal `agent_end`? If so the stuck-working sequence is unreachable; if the event is delivered asynchronously or for detached/background children, it is reachable.

@nwparker

Copy link
Copy Markdown
Contributor Author

Addressed the review findings:

  • Added the current async lifecycle aliases (subagent:async-started / subagent:async-complete) alongside task:subagent:lifecycle.
  • Persisted the roster and guarded listener registration on the Pi event bus so extension reloads do not duplicate subscriptions or lose children.
  • Reset the deferred completion marker at each new lead run and session boundary.
  • Added reload and alias regression tests.
  • Expanded the PR description with Fixes #18031, ELI5, proof, visual-proof status, testing, checklist, and AI disclosure.

The previous run had one test-shard and verify failures; this follow-up is running fresh CI now. I will merge only after all required checks pass.

1 similar comment
@nwparker

Copy link
Copy Markdown
Contributor Author

Addressed the review findings:

  • Added the current async lifecycle aliases (subagent:async-started / subagent:async-complete) alongside task:subagent:lifecycle.
  • Persisted the roster and guarded listener registration on the Pi event bus so extension reloads do not duplicate subscriptions or lose children.
  • Reset the deferred completion marker at each new lead run and session boundary.
  • Added reload and alias regression tests.
  • Expanded the PR description with Fixes #18031, ELI5, proof, visual-proof status, testing, checklist, and AI disclosure.

The previous run had one test-shard and verify failures; this follow-up is running fresh CI now. I will merge only after all required checks pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/pi/agent-status-extension-omp-lifecycle.test.ts (1)

48-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert lifecycle listener cardinality after reload.

This assertion only checks posted hooks. Shared lifecycle state can suppress a second agent_start even if reload() adds another lifecycle listener. Expose the listener count or spy on event registration, then assert that reload leaves exactly one task:subagent:lifecycle listener.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: abb1adfe-a6b3-4757-b767-5bba7cb36180

📥 Commits

Reviewing files that changed from the base of the PR and between 379eea2 and 0c3da6c.

📒 Files selected for processing (2)
  • src/main/pi/agent-status-extension-omp-lifecycle.test.ts
  • src/main/pi/agent-status-handler-source.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/pi/agent-status-handler-source.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.

@nwparker

Copy link
Copy Markdown
Contributor Author

Addressed the remaining CodeRabbit nitpick in commit 914f035: the reload regression test now asserts exactly one listener for each lifecycle channel (task lifecycle plus both async aliases), in addition to exercising the reloaded handler. Focused lifecycle suite passes: 13/13.

@nwparker
nwparker merged commit 35005fb into main Sep 21, 2026
4 checks passed
nplez1 added a commit to nplez1/orca that referenced this pull request Sep 21, 2026
…yai#21882

Upstream's 35005fb implemented the same "hold a pi pane working while async
subagents run" feature independently, and landed after the fork's descendant
series (4222703..0b0f8ac). Both then bound pi.events, so each
`subagent:async-started` / `:async-complete` fired twice, and for omp and
prime-agent the fork's deliberate silence on those channels was filled by
upstream's unconditional binding.

Converged on the fork's subsystem, because upstream's 39 lines are a narrow
subset of it: the fork binds both plugin lineages, reports the whole child
roster in every post, and holds the pane working receiver-side from that roster,
while upstream only withholds the lead's own agent_end. Channel coverage differs
in both directions, so upstream's OMP-only `task:subagent:lifecycle` gate stays
and only the two channels the fork owns were dropped from it.

Adapts upstream's alias test to the fork's contract: the aliases land in
`subagent_runs`, so it asserts the roster rather than upstream's withheld
agent_end.
nplez1 added a commit to nplez1/orca that referenced this pull request Sep 21, 2026
Sync log entry for the rebase: 84 commits replayed, 75 byte-identical and 9 adapted by
range-diff, none dropped; the six mechanical conflicts; and the two traps the gates
caught — upstream's new opencode2 provider missing from the fork's descendant Record, and
upstream's stablyai#21882 reimplementing the fork's pi async-subagent bus (converged on the fork's
subsystem). Also refreshes BRANCHES.md's last-updated line, the measured base drift, and
names the four environment gaps that make a raw full `pnpm test` look broken.
nplez1 added a commit to nplez1/orca that referenced this pull request Sep 22, 2026
…yai#21882

Upstream's 35005fb implemented the same "hold a pi pane working while async
subagents run" feature independently, and landed after the fork's descendant
series (4222703..0b0f8ac). Both then bound pi.events, so each
`subagent:async-started` / `:async-complete` fired twice, and for omp and
prime-agent the fork's deliberate silence on those channels was filled by
upstream's unconditional binding.

Converged on the fork's subsystem, because upstream's 39 lines are a narrow
subset of it: the fork binds both plugin lineages, reports the whole child
roster in every post, and holds the pane working receiver-side from that roster,
while upstream only withholds the lead's own agent_end. Channel coverage differs
in both directions, so upstream's OMP-only `task:subagent:lifecycle` gate stays
and only the two channels the fork owns were dropped from it.

Adapts upstream's alias test to the fork's contract: the aliases land in
`subagent_runs`, so it asserts the roster rather than upstream's withheld
agent_end.
nplez1 added a commit to nplez1/orca that referenced this pull request Sep 22, 2026
Sync log entry for the rebase: 84 commits replayed, 75 byte-identical and 9 adapted by
range-diff, none dropped; the six mechanical conflicts; and the two traps the gates
caught — upstream's new opencode2 provider missing from the fork's descendant Record, and
upstream's stablyai#21882 reimplementing the fork's pi async-subagent bus (converged on the fork's
subsystem). Also refreshes BRANCHES.md's last-updated line, the measured base drift, and
names the four environment gaps that make a raw full `pnpm test` look broken.
nwparker pushed a commit that referenced this pull request Sep 25, 2026
…22533)

Awaited workflow children announce subagent:async-started but never get a
subagent:async-complete, so the #21882 roster kept their ids forever and
suppressed every later agent_end. Treat subagent:process-terminal (the
runner exiting) as the end signal for tracked runs, with a short grace so
runs that report their own completion keep settling through that path.
Also accept completion events that carry only runId.

Fixes #22527
nplez1 added a commit to nplez1/orca that referenced this pull request Sep 25, 2026
…yai#21882

Upstream's 35005fb implemented the same "hold a pi pane working while async
subagents run" feature independently, and landed after the fork's descendant
series (4222703..0b0f8ac). Both then bound pi.events, so each
`subagent:async-started` / `:async-complete` fired twice, and for omp and
prime-agent the fork's deliberate silence on those channels was filled by
upstream's unconditional binding.

Converged on the fork's subsystem, because upstream's 39 lines are a narrow
subset of it: the fork binds both plugin lineages, reports the whole child
roster in every post, and holds the pane working receiver-side from that roster,
while upstream only withholds the lead's own agent_end. Channel coverage differs
in both directions, so upstream's OMP-only `task:subagent:lifecycle` gate stays
and only the two channels the fork owns were dropped from it.

Adapts upstream's alias test to the fork's contract: the aliases land in
`subagent_runs`, so it asserts the roster rather than upstream's withheld
agent_end.
nplez1 added a commit to nplez1/orca that referenced this pull request Sep 25, 2026
Sync log entry for the rebase: 84 commits replayed, 75 byte-identical and 9 adapted by
range-diff, none dropped; the six mechanical conflicts; and the two traps the gates
caught — upstream's new opencode2 provider missing from the fork's descendant Record, and
upstream's stablyai#21882 reimplementing the fork's pi async-subagent bus (converged on the fork's
subsystem). Also refreshes BRANCHES.md's last-updated line, the measured base drift, and
names the four environment gaps that make a raw full `pnpm test` look broken.
nplez1 added a commit to nplez1/orca that referenced this pull request Sep 27, 2026
…yai#21882

Upstream's 35005fb implemented the same "hold a pi pane working while async
subagents run" feature independently, and landed after the fork's descendant
series (4222703..0b0f8ac). Both then bound pi.events, so each
`subagent:async-started` / `:async-complete` fired twice, and for omp and
prime-agent the fork's deliberate silence on those channels was filled by
upstream's unconditional binding.

Converged on the fork's subsystem, because upstream's 39 lines are a narrow
subset of it: the fork binds both plugin lineages, reports the whole child
roster in every post, and holds the pane working receiver-side from that roster,
while upstream only withholds the lead's own agent_end. Channel coverage differs
in both directions, so upstream's OMP-only `task:subagent:lifecycle` gate stays
and only the two channels the fork owns were dropped from it.

Adapts upstream's alias test to the fork's contract: the aliases land in
`subagent_runs`, so it asserts the roster rather than upstream's withheld
agent_end.
nplez1 added a commit to nplez1/orca that referenced this pull request Sep 27, 2026
Sync log entry for the rebase: 84 commits replayed, 75 byte-identical and 9 adapted by
range-diff, none dropped; the six mechanical conflicts; and the two traps the gates
caught — upstream's new opencode2 provider missing from the fork's descendant Record, and
upstream's stablyai#21882 reimplementing the fork's pi async-subagent bus (converged on the fork's
subsystem). Also refreshes BRANCHES.md's last-updated line, the measured base drift, and
names the four environment gaps that make a raw full `pnpm test` look broken.
nplez1 added a commit to nplez1/orca that referenced this pull request Sep 29, 2026
…yai#21882

Upstream's 35005fb implemented the same "hold a pi pane working while async
subagents run" feature independently, and landed after the fork's descendant
series (4222703..0b0f8ac). Both then bound pi.events, so each
`subagent:async-started` / `:async-complete` fired twice, and for omp and
prime-agent the fork's deliberate silence on those channels was filled by
upstream's unconditional binding.

Converged on the fork's subsystem, because upstream's 39 lines are a narrow
subset of it: the fork binds both plugin lineages, reports the whole child
roster in every post, and holds the pane working receiver-side from that roster,
while upstream only withholds the lead's own agent_end. Channel coverage differs
in both directions, so upstream's OMP-only `task:subagent:lifecycle` gate stays
and only the two channels the fork owns were dropped from it.

Adapts upstream's alias test to the fork's contract: the aliases land in
`subagent_runs`, so it asserts the roster rather than upstream's withheld
agent_end.
nplez1 added a commit to nplez1/orca that referenced this pull request Sep 29, 2026
Sync log entry for the rebase: 84 commits replayed, 75 byte-identical and 9 adapted by
range-diff, none dropped; the six mechanical conflicts; and the two traps the gates
caught — upstream's new opencode2 provider missing from the fork's descendant Record, and
upstream's stablyai#21882 reimplementing the fork's pi async-subagent bus (converged on the fork's
subsystem). Also refreshes BRANCHES.md's last-updated line, the measured base drift, and
names the four environment gaps that make a raw full `pnpm test` look broken.
nplez1 added a commit to nplez1/orca that referenced this pull request Oct 1, 2026
…yai#21882

Upstream's 35005fb implemented the same "hold a pi pane working while async
subagents run" feature independently, and landed after the fork's descendant
series (4222703..0b0f8ac). Both then bound pi.events, so each
`subagent:async-started` / `:async-complete` fired twice, and for omp and
prime-agent the fork's deliberate silence on those channels was filled by
upstream's unconditional binding.

Converged on the fork's subsystem, because upstream's 39 lines are a narrow
subset of it: the fork binds both plugin lineages, reports the whole child
roster in every post, and holds the pane working receiver-side from that roster,
while upstream only withholds the lead's own agent_end. Channel coverage differs
in both directions, so upstream's OMP-only `task:subagent:lifecycle` gate stays
and only the two channels the fork owns were dropped from it.

Adapts upstream's alias test to the fork's contract: the aliases land in
`subagent_runs`, so it asserts the roster rather than upstream's withheld
agent_end.
nplez1 added a commit to nplez1/orca that referenced this pull request Oct 1, 2026
Sync log entry for the rebase: 84 commits replayed, 75 byte-identical and 9 adapted by
range-diff, none dropped; the six mechanical conflicts; and the two traps the gates
caught — upstream's new opencode2 provider missing from the fork's descendant Record, and
upstream's stablyai#21882 reimplementing the fork's pi async-subagent bus (converged on the fork's
subsystem). Also refreshes BRANCHES.md's last-updated line, the measured base drift, and
names the four environment gaps that make a raw full `pnpm test` look broken.
nplez1 added a commit to nplez1/orca that referenced this pull request Oct 6, 2026
…yai#21882

Upstream's 35005fb implemented the same "hold a pi pane working while async
subagents run" feature independently, and landed after the fork's descendant
series (4222703..0b0f8ac). Both then bound pi.events, so each
`subagent:async-started` / `:async-complete` fired twice, and for omp and
prime-agent the fork's deliberate silence on those channels was filled by
upstream's unconditional binding.

Converged on the fork's subsystem, because upstream's 39 lines are a narrow
subset of it: the fork binds both plugin lineages, reports the whole child
roster in every post, and holds the pane working receiver-side from that roster,
while upstream only withholds the lead's own agent_end. Channel coverage differs
in both directions, so upstream's OMP-only `task:subagent:lifecycle` gate stays
and only the two channels the fork owns were dropped from it.

Adapts upstream's alias test to the fork's contract: the aliases land in
`subagent_runs`, so it asserts the roster rather than upstream's withheld
agent_end.
nplez1 added a commit to nplez1/orca that referenced this pull request Oct 6, 2026
Sync log entry for the rebase: 84 commits replayed, 75 byte-identical and 9 adapted by
range-diff, none dropped; the six mechanical conflicts; and the two traps the gates
caught — upstream's new opencode2 provider missing from the fork's descendant Record, and
upstream's stablyai#21882 reimplementing the fork's pi async-subagent bus (converged on the fork's
subsystem). Also refreshes BRANCHES.md's last-updated line, the measured base drift, and
names the four environment gaps that make a raw full `pnpm test` look broken.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pi appears finished while async pi-subagents are still running

1 participant