Skip to content

Harden workforce persona spawn readiness#153

Merged
khaliqgant merged 2 commits into
mainfrom
fix/persona-spawn-readiness
Jun 7, 2026
Merged

Harden workforce persona spawn readiness#153
khaliqgant merged 2 commits into
mainfrom
fix/persona-spawn-readiness

Conversation

@kjgbot
Copy link
Copy Markdown
Contributor

@kjgbot kjgbot commented Jun 7, 2026

Summary

  • launch workforce personas through AgentWorkforce's default sandbox path instead of forcing --install-in-repo, preserving sidecar materialization and skill staging
  • add a two-phase persona readiness gate: wait for sandbox-ready worker output, then require strict broker delivery_injected confirmation
  • hide pending persona wrappers from broker rosters/integration-event routing, release unready wrappers, clear stale routing state, and invalidate integration recipient caches around persona spawn
  • add regressions for persona args, readiness success, wrapper cleanup, hidden pending agents, delivery readiness failure, and stale worker-stream replay

Tests

  • npx --yes vitest run src/main/broker.test.ts
  • npx tsc --noEmit
  • npm test

@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Jun 7, 2026

Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds workforce persona harness readiness verification: after a persona is spawned and registered with the broker, the system marks it pending, waits for harness mount success from worker-stream output, sends an injected probe message to confirm broker delivery capability, and releases the persona if verification fails. Unverified personas are filtered from agent listings. Test mocks now support event history and listener subscriptions. The spawn handler wraps cache invalidation in try/finally.

Changes

Persona Readiness Verification

Layer / File(s) Summary
Mock client event infrastructure
src/main/broker.test.ts
MockClient now records eventHistory, exposes queryEvents(filter), supports onEvent(listener) with unsubscribe, and sendMessage returns { event_id, targets } and injects delivery_injected events for direct agent targets; test suite captures/restores PEAR_PERSONA_HARNESS_READY_TIMEOUT_MS and adds emitPersonaHarnessReady.
Spawn scheduling & assertions
src/main/broker.test.ts
Harness-ready emission is scheduled via setImmediate after spawnPty; assertions tightened to ensure spawnPty is not called with installation-related flags.
Readiness gating tests
src/main/broker.test.ts
New and updated tests verify harness readiness timeout releases workforce wrappers, listAgents/listBrokerDetails exclude pending personas until readiness, delivery-probe failures release personas, and prior harness-ready output is not reused across launches.
Readiness tracking state & utilities
src/main/broker.ts
Adds timeout constants, PersonaBrokerSpawnResult extension, worker-stream parsing helpers, and pendingPersonaReadiness bookkeeping helpers.
Readiness verification pipeline
src/main/broker.ts
Marks personas readiness-pending during spawn, computes worker-stream baseline, tails post-baseline chunks to detect harness-ready/failed output, injects a delivery probe (sendMessageAndWaitForInjected) to confirm broker delivery, releases on failure, and clears pending status in a finally block.
Agent listing filters
src/main/broker.ts
listAgents and listBrokerDetails filter out personas with pending readiness so unverified personas do not appear as available.
Spawn handler cache invalidation & tests
src/main/ipc-handlers.ts, src/main/ipc-handlers.test.ts
broker:spawn-persona handler now invalidates project agent cache in a finally block; tests add a shared integrationEventBridge mock and verify the handler's cache invalidation and returned persona payload.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • AgentWorkforce/pear#148: Defines and tests the sendMessageAndWaitForInjected / delivery_injected confirmation flow relied on by this readiness probe.
  • AgentWorkforce/pear#136: Related changes to persona spawn return shapes and spawn invocation arguments that intersect with updated spawn behavior and tests.

Poem

🐰 I hopped to the broker, a probe in my paw,
Streams whispered "ready" and the harness I saw—
If silence returns or the probe fails to land,
The persona is freed by a careful hand.
Now listings stay honest, each agent well-tried.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Harden workforce persona spawn readiness' is specific and clearly summarizes the main change: adding readiness gating and validation for workforce persona spawning.
Description check ✅ Passed The description is directly related to the changeset, detailing the key improvements: sandbox path changes, two-phase readiness gating, wrapper hiding/cleanup, and test coverage.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/persona-spawn-readiness

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 and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a verification mechanism for Workforce persona harness and broker delivery readiness before they are exposed to the system. The review identified two critical issues: a potential state leak in pendingPersonaReadiness if broker registration verification fails and throws an error, and a race condition in waitForPersonaHarnessReadyOutput that can cause duplicate event processing and corrupt the readiness check output.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/main/broker.ts
Comment on lines +2557 to 2579
this.markPersonaReadinessPending(session, spawned.name)
try {
await this.verifyPersonaHarnessReady(session, spawned.name, trimmedPersonaId, {
workerStreamBaselineSeq: spawned.workerStreamBaselineSeq,
workerStreamBaselineCount: spawned.workerStreamBaselineCount
})
} catch (err) {
await this.releaseUnreadyPersona(session, spawned.name, 'persona harness readiness verification failed')
throw err
} finally {
this.clearPersonaReadinessPending(session, spawned.name)
}
return {
name: spawned.name,
runtime: spawned.runtime,
...(spawned.cli ? { cli: spawned.cli } : {})
}
}

await session.client.release(spawned.name, 'persona broker registration verification failed').catch((err) => {
if (!isMissingAgentError(err)) {
console.warn(`[broker] Failed to release unverified persona agent ${spawned.name}:`, err)
}
})
await this.releaseUnreadyPersona(session, spawned.name, 'persona broker registration verification failed')
throw new Error(
`Workforce persona ${trimmedPersonaId} launched but did not stay registered with the broker`
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If verifyPersonaBrokerRegistration (called at line 2546) throws an error (e.g., due to a network or broker connection failure during listAgents), the error will propagate immediately. Because spawnPersonaWithMode has already marked the agent as pending, and the error prevents entering the try...finally block or reaching the releaseUnreadyPersona call at the end of the method, the agent's name will be permanently leaked in pendingPersonaReadiness. This will cause the agent to be permanently hidden from listAgents calls. Consider wrapping the registration verification in a try...catch block to ensure releaseUnreadyPersona is always called on any failure.

Comment thread src/main/broker.ts
Comment on lines +2824 to +2835
const observe = (event: BrokerEvent): void => {
if (!isWorkerStreamForAgent(event, name)) return
output += brokerEventChunk(event)
const failure = personaHarnessFailedFromOutput(output)
if (failure) {
finish(new Error(`Workforce persona ${personaId} failed during harness startup: ${failure}`))
return
}
if (personaHarnessReadyFromOutput(output)) {
finish()
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

There is a potential race condition and double-processing of events in waitForPersonaHarnessReadyOutput. Because the live event listener is registered via session.client.onEvent before querying the historical events via this.workerStreamEvents, any event emitted during or shortly after subscription can be processed both by the live listener and by the history loop. This will cause chunks to be double-appended to output, corrupting the accumulated string and potentially failing the readiness check or regex matching. We should deduplicate events by tracking processed sequence numbers (seq) or event IDs.

    const processedSeqs = new Set<number>()
    const processedIds = new Set<string>()

    const observe = (event: BrokerEvent): void => {
      if (!isWorkerStreamForAgent(event, name)) return
      const seq = brokerEventNumber(event, 'seq')
      const id = brokerEventString(event, 'event_id') || brokerEventString(event, 'id')
      if (seq !== undefined) {
        if (processedSeqs.has(seq)) return
        processedSeqs.add(seq)
      } else if (id !== undefined) {
        if (processedIds.has(id)) return
        processedIds.add(id)
      }
      output += brokerEventChunk(event)
      const failure = personaHarnessFailedFromOutput(output)
      if (failure) {
        finish(new Error(`Workforce persona ${personaId} failed during harness startup: ${failure}`))
        return
      }
      if (personaHarnessReadyFromOutput(output)) {
        finish()
      }
    }

@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Jun 7, 2026

Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI.

@agent-relay-code
Copy link
Copy Markdown
Contributor

Implemented the PR fixes I found:

  • Removed the extra pre-spawn integrationEventBridge.invalidateProjectAgentCache call for broker:spawn-persona; cache invalidation now happens once after spawn settles.
  • Filtered pending persona readiness agents out of listBrokerDetails() / broker status snapshots, matching the existing listAgents() behavior.
  • Added regression tests for both paths.

Verification run:

  • npx vitest run src/main/broker.test.ts src/main/ipc-handlers.test.ts passed.
  • npm run build passed.
  • npm test passed.
  • npx tsc -p tsconfig.node.json --noEmit is not currently a valid repo verifier; it fails broadly on existing config/test typing issues unrelated to these edits.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/broker.ts`:
- Around line 2748-2769: The current workerStreamEvents uses a duck-typed type
assertion and runtime typeof check for queryEvents; instead, update the
BrokerSession/client typing so session.client is typed to include queryEvents
(the same surface used elsewhere like readAgentOutput and mocked in
broker.test.ts) and call session.client.queryEvents(...) directly without the
typeof check; adjust workerStreamEvents and workerStreamBaseline to rely on the
stronger compile-time type (or narrow via a proper interface) and remove the
fallback to [] so the code uses the client API consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 23eb40fa-8222-4370-84a0-e24c25819d8f

📥 Commits

Reviewing files that changed from the base of the PR and between 6721107 and 6c45a02.

📒 Files selected for processing (3)
  • src/main/broker.test.ts
  • src/main/broker.ts
  • src/main/ipc-handlers.ts

Comment thread src/main/broker.ts
Comment on lines +2748 to +2769
private workerStreamEvents(session: BrokerSession, name: string, limit: number): BrokerEvent[] {
const queryEvents = (session.client as unknown as {
queryEvents?: (filter: { kind: string; name: string; limit: number }) => BrokerEvent[]
}).queryEvents
return typeof queryEvents === 'function'
? queryEvents.call(session.client, { kind: 'worker_stream', name, limit })
: []
}

private workerStreamBaseline(session: BrokerSession, name: string): {
workerStreamBaselineSeq?: number
workerStreamBaselineCount: number
} {
const events = this.workerStreamEvents(session, name, 1000)
const seqs = events
.map((event) => brokerEventNumber(event, 'seq'))
.filter((seq): seq is number => seq !== undefined)
return {
workerStreamBaselineSeq: seqs.length > 0 ? Math.max(...seqs) : undefined,
workerStreamBaselineCount: events.length
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if queryEvents is documented or used elsewhere in the codebase
rg -n "queryEvents" --type ts -C2

Repository: AgentWorkforce/pear

Length of output: 1804


Remove the “undocumented/internal API” concern for queryEvents and narrow the remaining issue

queryEvents is used directly in src/main/broker.ts (e.g., readAgentOutput calls session.client.queryEvents(...)) and is also mocked in src/main/broker.test.ts, so it’s part of the expected client surface in this codebase. The only remaining concern is that workerStreamEvents re-introduces duck-typing/type-assertion (typeof queryEvents === 'function' ? ... : []) rather than relying on the client’s typings (if they already include queryEvents).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/broker.ts` around lines 2748 - 2769, The current workerStreamEvents
uses a duck-typed type assertion and runtime typeof check for queryEvents;
instead, update the BrokerSession/client typing so session.client is typed to
include queryEvents (the same surface used elsewhere like readAgentOutput and
mocked in broker.test.ts) and call session.client.queryEvents(...) directly
without the typeof check; adjust workerStreamEvents and workerStreamBaseline to
rely on the stronger compile-time type (or narrow via a proper interface) and
remove the fallback to [] so the code uses the client API consistently.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/ipc-handlers.test.ts`:
- Around line 213-223: Add a failing-path test for the 'broker:spawn-persona'
handler: simulate mock.brokerManager.spawnPersona rejecting (throwing) when
invoked, call the handler (e.g., handler?.({}, 'project-1', 'autonomous-actor'))
and assert that mock.integrationEventBridge.invalidateProjectAgentCache was
still called with 'project-1' even on rejection; optionally add a second
call/replay scenario to ensure duplicate/replay behavior still triggers
invalidateProjectAgentCache. Ensure you reference the same handler key
('broker:spawn-persona'), mock.brokerManager.spawnPersona, and
mock.integrationEventBridge.invalidateProjectAgentCache when adding the
assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b2a50c4d-b836-41c3-9e38-b3e3f13dd2be

📥 Commits

Reviewing files that changed from the base of the PR and between 6c45a02 and f467fa0.

📒 Files selected for processing (4)
  • src/main/broker.test.ts
  • src/main/broker.ts
  • src/main/ipc-handlers.test.ts
  • src/main/ipc-handlers.ts
💤 Files with no reviewable changes (1)
  • src/main/ipc-handlers.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/broker.ts
  • src/main/broker.test.ts

Comment on lines +213 to +223
it('invalidates the integration agent cache after persona spawn settles', async () => {
const handler = mock.handlers.get('broker:spawn-persona')
expect(handler).toBeTypeOf('function')

const result = await handler?.({}, 'project-1', 'autonomous-actor')

expect(result).toEqual({ name: 'persona', runtime: 'pty', cli: 'claude' })
expect(mock.brokerManager.spawnPersona).toHaveBeenCalledWith('project-1', 'autonomous-actor')
expect(mock.integrationEventBridge.invalidateProjectAgentCache).toHaveBeenCalledTimes(1)
expect(mock.integrationEventBridge.invalidateProjectAgentCache).toHaveBeenCalledWith('project-1')
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add the failure-path regression for broker:spawn-persona cache invalidation.

This suite only verifies the success path. The contract in ipc-handlers.ts is “invalidate cache after spawn settles” (success or throw), so you should also assert invalidation when spawnPersona rejects (and optionally on repeated calls to cover replay/duplicate behavior).

Suggested test addition
+  it('invalidates the integration agent cache when persona spawn fails', async () => {
+    const handler = mock.handlers.get('broker:spawn-persona')
+    expect(handler).toBeTypeOf('function')
+
+    mock.brokerManager.spawnPersona.mockRejectedValueOnce(new Error('spawn failed'))
+
+    await expect(handler?.({}, 'project-1', 'autonomous-actor')).rejects.toThrow('spawn failed')
+    expect(mock.integrationEventBridge.invalidateProjectAgentCache).toHaveBeenCalledWith('project-1')
+  })

As per coding guidelines, tests touching spawned personas/integration notifications must include regression coverage beyond happy path (including duplicate/replay-style cases).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ipc-handlers.test.ts` around lines 213 - 223, Add a failing-path
test for the 'broker:spawn-persona' handler: simulate
mock.brokerManager.spawnPersona rejecting (throwing) when invoked, call the
handler (e.g., handler?.({}, 'project-1', 'autonomous-actor')) and assert that
mock.integrationEventBridge.invalidateProjectAgentCache was still called with
'project-1' even on rejection; optionally add a second call/replay scenario to
ensure duplicate/replay behavior still triggers invalidateProjectAgentCache.
Ensure you reference the same handler key ('broker:spawn-persona'),
mock.brokerManager.spawnPersona, and
mock.integrationEventBridge.invalidateProjectAgentCache when adding the
assertions.

Sources: Coding guidelines, Learnings

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 4 files

Re-trigger cubic

@khaliqgant khaliqgant merged commit 550a4f5 into main Jun 7, 2026
4 checks passed
@khaliqgant khaliqgant deleted the fix/persona-spawn-readiness branch June 7, 2026 20:29
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.

2 participants