Skip to content

Upgrade to agent-relay v8 + terminal latency (predictive echo)#81

Merged
willwashburn merged 1 commit into
mainfrom
feat/relay-v8-terminal-latency
Jun 4, 2026
Merged

Upgrade to agent-relay v8 + terminal latency (predictive echo)#81
willwashburn merged 1 commit into
mainfrom
feat/relay-v8-terminal-latency

Conversation

@willwashburn
Copy link
Copy Markdown
Member

@willwashburn willwashburn commented Jun 4, 2026

User description

What

Two related changes that ship together (predictive echo requires the v8 runtime):

1. agent-relay v7 → v8 migration

  • Broker integration moves from @agent-relay/sdk (v7) to @agent-relay/harness-driver (v8.1.2); HarnessDriverClient replaces AgentRelayClient.
  • Broker binary resolution prefers the v8 per-platform optional package (@agent-relay/broker-${platform}-${arch}) and falls back to the SDK-bundled binary, with asar-unpacking applied to both paths for packaged builds.
  • Bumps agent-relay, @agent-relay/sdk, @agent-relay/cloud, @agent-relay/harness-driver, and the bundled broker binary to 8.1.2.

2. Terminal latency improvements

v8.1.1/8.1.2 (relay #1045) brought the broker-side wins for free — ~16ms leading-edge PTY output flush and FIFO input pipelining. On top of that:

  • Predictive (mosh-style) local echo (src/renderer/src/lib/predictive-echo.ts + use-terminal.ts): optimistically renders printable keystrokes (underlined) and reconciles against authoritative PTY output, rolling back mispredictions. Backed by a dedicated headless xterm as the confirmed-screen model so predictions never corrupt reconciliation. Adaptive on measured latency — invisible/dormant on fast local links.
  • SRTT wiring: the broker's PtyInputStream input→ack SRTT is exposed via pear.broker.inputSrtt(...) and polled into the engine's adaptive engage decision, so prediction engages from the first acked keystroke and re-engages promptly when latency spikes (rather than waiting to measure a full echo-confirmation cycle).

The predictive-echo engine is consumed via the @agent-relay/harness-driver/predictive-echo subpath, added upstream in relay #1048 (shipped in 8.1.2) so the renderer doesn't pull in the node-only broker transport.

Testing

  • npm run build
  • npm test (node) — 22 pass
  • npx vitest run — 28 pass (4 files)

Notes

  • This branch was rebased onto current main (which had a large refactor since the work began); the broker binary-resolution conflict and the @relayfile/sdk import (root, not /cli, for the 0.7.23 transitive version on main) were reconciled.
  • Pre-existing tsc --noEmit warnings in cloud-agent.ts (Project.cloudAgent) and a few renderer files are unchanged tech debt on main (the build uses esbuild/transpile-only) — not introduced here.

🤖 Generated with Claude Code


CodeAnt-AI Description

Upgrade Agent Relay and add predictive terminal echo

What Changed

  • Pear now runs on Agent Relay v8, which updates the broker runtime and keeps packaged builds working with the new broker binary layout.
  • The terminal can now show fast local keystrokes immediately on slower connections, then corrects them when the agent output arrives.
  • Terminal prediction now starts sooner and recovers faster after latency spikes by using the broker’s measured input round-trip time.
  • Persona listing and launch now use the updated workforce command output, so persona details and persona-based agent start up keep working with the new runtime.

Impact

✅ Faster typing response in remote terminals
✅ Fewer delays before agent output appears
✅ More reliable persona launch on Agent Relay v8

🔄 Retrigger CodeAnt AI Review

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Migrate the broker integration from @agent-relay/sdk (v7) to
@agent-relay/harness-driver (v8.1.2), and add predictive local echo
to the terminal:

- Bump agent-relay packages + bundled broker binary to 8.1.2.
- Predictive (mosh-style) echo: optimistically render printable
  keystrokes and reconcile against authoritative PTY output. Adaptive
  on measured latency, so it stays invisible on fast local links.
- Feed the broker's PtyInputStream input→ack SRTT into the engine's
  adaptive engage decision so prediction engages promptly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Jun 4, 2026

CodeAnt AI is reviewing your PR.

@gemini-code-assist
Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: e6341308-400a-4998-9e12-09298bb65fc5

📥 Commits

Reviewing files that changed from the base of the PR and between 4bdb762 and 2524b86.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • .gitignore
  • electron.vite.config.ts
  • package.json
  • src/main/__tests__/burn-spawn-hook.test.ts
  • src/main/broker.ts
  • src/main/burn-spawn-hook.ts
  • src/main/cloud-agent.test.ts
  • src/main/cloud-agent.ts
  • src/main/ipc-handlers.ts
  • src/preload/index.ts
  • src/renderer/src/hooks/use-terminal.ts
  • src/renderer/src/lib/predictive-echo.ts
  • src/renderer/src/stores/agent-store.ts
  • src/shared/types/ipc.ts

📝 Walkthrough

Walkthrough

This PR migrates the Pear app from @agent-relay/sdk to @agent-relay/harness-driver, adds AgentWorkforce-based persona discovery with MCP command resolution, exposes input stream latency metrics via a new IPC handler, and integrates a predictive local-echo layer into the terminal renderer for low-latency user feedback during PTY interactions.

Changes

Dependency Migration & Predictive Echo Integration

Layer / File(s) Summary
Build Configuration & Dependency Updates
.gitignore, electron.vite.config.ts, package.json
Gitignore ignores /agentworkforce/relay path, Vite config excludes @agent-relay/harness-driver from externalization, and package.json updates @agent-relay/* and @relayburn/sdk versions.
Client API Migration to HarnessDriver
src/main/broker.ts, src/main/burn-spawn-hook.ts, src/main/cloud-agent.ts, src/main/ipc-handlers.ts, src/main/__tests__/burn-spawn-hook.test.ts, src/main/cloud-agent.test.ts
Imports switch from @agent-relay/sdk to @agent-relay/harness-driver; SpawnPatch and BeforeAgentSpawnContext types are now defined locally in burn-spawn-hook.ts using SpawnPtyInput from harness-driver; test mocks updated to stub HarnessDriverClient.
Broker Helpers for Package Version & Binary Resolution
src/main/broker.ts
Adds resolvePackageVersion() to read dependency versions from package.json, updates broker binary resolution to prefer platform-specific @agent-relay/broker-* optional packages with SDK fallback, and bumps CLI version constants.
AgentWorkforce Persona Discovery & MCP Command Resolution
src/main/broker.ts
Introduces helpers to resolve Node command, locate Agent Relay MCP script, and compute effective MCP command from env/bundled/package/npx paths; adds workforce persona JSON parsing and normalization types; updates listPersonas() and spawnPersona() to use workforce-native resolution; conditionally injects resolved MCP command into broker spawn environment.
Input Stream Latency Metrics Infrastructure
src/main/broker.ts, src/main/ipc-handlers.ts, src/preload/index.ts, src/shared/types/ipc.ts
Adds getInputSrtt(projectId, name) method to BrokerManager exposing smoothed PTY stream latency, wires new broker:input-srtt IPC handler, exposes broker.inputSrtt in preload API, and registers method on PearAPI.broker interface.
Predictive Echo Engine Module
src/renderer/src/lib/predictive-echo.ts
New renderer module implementing createPredictiveEcho factory that wires a PredictiveEchoEngine to live xterm output while using a headless Terminal for authoritative screen state; engine receives SRTT via callback for adaptive prediction engagement.
Terminal Hook Predictive Echo Integration
src/renderer/src/hooks/use-terminal.ts
Integrates predictive echo into useTerminal: creates engine on init with SRTT polling interval, routes user input/server output/resize events through engine for local-echo prediction, seeds authoritative state from snapshots, and cleans up resources on unmount.
Documentation & Comment Updates
src/renderer/src/stores/agent-store.ts
Comment updated to reference @agent-relay/harness-driver as the source for BrokerEvent types.

🎯 4 (Complex) | ⏱️ ~75 minutes

🐰 With harness-driver in hand and personas found,
We predict the echo before it sounds.
Input latency flows through IPC's gate,
As predictive typing smooths the wait.
Relay rebound, the terminal takes flight!


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

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

@codeant-ai codeant-ai Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files label Jun 4, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2524b868cb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/broker.ts
__dirname, '..', '..', 'node_modules', '@agent-relay', `broker-${suffix}`, 'bin',
process.platform === 'win32' ? 'agent-relay-broker.exe' : 'agent-relay-broker'
)
if (canExecute(optionalPackageBinary)) return unpackIfPackaged(optionalPackageBinary)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Unpack the new broker optional package

When this runs in a packaged app, line 330 rewrites the v8 optional-package broker path to app.asar.unpacked, but electron-builder.yml still only unpacks node_modules/@agent-relay/sdk/bin/**. Since the new node_modules/@agent-relay/broker-${suffix}/bin/... files are not placed under app.asar.unpacked, packaged local broker startup will be handed a missing/non-executable path after the v8 upgrade. Please add the @agent-relay/broker-*/bin/** packages to asarUnpack (or otherwise keep this path executable).

Useful? React with 👍 / 👎.

Comment thread src/main/broker.ts
const bundledMcpScript = resolveBundledAgentRelayMcpScript()
const nodeCommand = bundledMcpScript ? resolveNodeCommandForMcp() : undefined
if (bundledMcpScript && nodeCommand) {
return `${nodeCommand} ${bundledMcpScript}`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The MCP command is built as a single unquoted string, so paths containing spaces (common on Windows/macOS app install locations) will be split incorrectly and fail to execute. Quote/escape command components or pass executable and args separately to avoid path-splitting failures. [possible bug]

Severity Level: Major ⚠️
- ⚠️ MCP servers fail to start when Node path has spaces.
- ⚠️ Agents relying on MCP tools silently lose functionality.
Steps of Reproduction ✅
1. When starting a local broker, the renderer calls `pear.broker.start()`
(`src/preload/index.ts:61-62`), which is handled by `'broker:start'` IPC in
`src/main/ipc-handlers.ts:173-183`, calling `brokerManager.start(...)` in
`src/main/broker.ts:981-1115`.

2. In `BrokerManager.start`, before spawning the broker, `const agentRelayMcpCommand =
resolveAgentRelayMcpCommand()` is evaluated at `src/main/broker.ts:1055`, and if non-empty
it is passed through to the child via `env: { AGENT_RELAY_MCP_COMMAND:
agentRelayMcpCommand }` in the spawn options at `src/main/broker.ts:1062-1068`.

3. `resolveAgentRelayMcpCommand()` (`src/main/broker.ts:165-183`) resolves a Node
executable and the bundled MCP script: `bundledMcpScript` is found by
`resolveBundledAgentRelayMcpScript()` (lines 149-163) and `nodeCommand` is resolved by
`resolveNodeCommandForMcp()` (lines 140-147), which typically returns a path like
`C:\Program Files\nodejs\node.exe` on Windows. When both are present, the command is
constructed as a single unquoted string `\`${nodeCommand} ${bundledMcpScript}\`` at line
172.

4. The broker process (spawned via `AgentRelayClient.spawn(opts)` at
`src/main/broker.ts:1075`) reads `AGENT_RELAY_MCP_COMMAND` as a raw command string and
executes it using its own command-line parsing. Because `nodeCommand` can contain spaces
(e.g. under `Program Files` on Windows or any custom install path with spaces), composing
`C:\Program Files\nodejs\node.exe C:\path\to\agent-relay-mcp.js` without quoting or
splitting into `{file, args}` causes downstream parsing to split on spaces and treat
`C:\Program` as the executable. This mis-parsing breaks MCP startup (Node executable or
script cannot be found), so attempts by the broker to launch MCP servers for tools fail
whenever the Node or MCP script path includes spaces.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/main/broker.ts
**Line:** 172:172
**Comment:**
	*Possible Bug: The MCP command is built as a single unquoted string, so paths containing spaces (common on Windows/macOS app install locations) will be split incorrectly and fail to execute. Quote/escape command components or pass executable and args separately to avoid path-splitting failures.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment thread src/main/broker.ts
Comment on lines +322 to +330
const unpackIfPackaged = (binary: string): string =>
app.isPackaged ? binary.replace('app.asar', 'app.asar.unpacked') : binary

// v8 ships the broker as a per-platform optional package (@agent-relay/broker-*).
const optionalPackageBinary = join(
__dirname, '..', '..', 'node_modules', '@agent-relay', `broker-${suffix}`, 'bin',
process.platform === 'win32' ? 'agent-relay-broker.exe' : 'agent-relay-broker'
)
if (canExecute(optionalPackageBinary)) return unpackIfPackaged(optionalPackageBinary)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The optional v8 broker binary check is done on the packed app.asar path, then only rewritten to app.asar.unpacked after the check. In packaged builds this can incorrectly fail the canExecute test and skip a valid unpacked optional binary, causing fallback to a path that may not exist. Apply the asar-unpack rewrite before the executability check. [logic error]

Severity Level: Critical 🚨
- ❌ Packaged Pear app fails to spawn local broker binary.
- ❌ Terminal attach operations fail; no PTY-backed agent sessions.
- ⚠️ Local Relay-powered features become unavailable for all projects.
Steps of Reproduction ✅
1. In the renderer, opening a project starts the local broker via `pear.broker.start()` in
`src/preload/index.ts:61-62`, which invokes IPC channel `'broker:start'`.

2. The main process handles `'broker:start'` in `src/main/ipc-handlers.ts:173-183`,
calling `brokerManager.start(projectId, cwd, name, win, channels)` implemented in
`src/main/broker.ts:981-1115`.

3. Inside `BrokerManager.start`, when no existing broker is found, it prepares spawn
options with `binaryPath: resolveBundledBrokerBinary()` at `src/main/broker.ts:1062-1068`,
so every local broker start calls `resolveBundledBrokerBinary()`.

4. In `resolveBundledBrokerBinary()` (`src/main/broker.ts:310-338`), the optional v8
broker path is computed as `optionalPackageBinary` at lines 321-329. In a packaged build
(`app.isPackaged === true`), the real binary is unpacked under an `app.asar.unpacked`
path, but executability is checked against the *packed* path: `if
(canExecute(optionalPackageBinary))` at line 330, only rewriting to the unpacked path via
`unpackIfPackaged` on return. This causes `canExecute` to fail even when the unpacked
binary exists, so the code wrongly skips the optional broker and falls through to the
legacy SDK path `brokerBinary` (lines 334-338). If the SDK no longer ships a per-platform
broker binary at that fallback path (the intended v8 layout),
`AgentRelayClient.spawn(opts)` at `src/main/broker.ts:1075` will attempt to spawn a
non-existent binary and the broker fails to start in production builds.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/main/broker.ts
**Line:** 322:330
**Comment:**
	*Logic Error: The optional v8 broker binary check is done on the packed `app.asar` path, then only rewritten to `app.asar.unpacked` after the check. In packaged builds this can incorrectly fail the `canExecute` test and skip a valid unpacked optional binary, causing fallback to a path that may not exist. Apply the asar-unpack rewrite before the executability check.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

// Prime the engine's confirmed-screen model with the same bytes (this
// does not re-write to the terminal) so its cursor matches the real
// screen before any prediction is made.
await predictiveEchoRef.current?.seed(result.snapshot.screen)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Awaiting predictive-echo seeding before subscribing to buffered PTY chunks increases the attach race window and can skip output that arrives between snapshot write and buffer subscription. Seed without delaying replay-state handoff (or subscribe first) so no server output is dropped during attach. [race condition]

Severity Level: Major ⚠️
- ⚠️ Terminal intermittently drops PTY output during attach handshake.
- ⚠️ Users may miss initial prompts or command responses.
Steps of Reproduction ✅
1. A user opens an agent terminal in the UI, which uses `useTerminal()` from
`src/renderer/src/hooks/use-terminal.ts:141-148`. That hook wires the terminal to the
broker via IPC (`pear.broker.attachTerminal`, `pear.broker.onPtyChunk`) defined in
`src/preload/index.ts:80-101`.

2. Inside `useTerminal`, the main effect (lines 173-470) initializes the xterm instance in
`init()` (lines 305-413). After creating the `Terminal`, predictive-echo engine, and
sizing it, the code calls `attachAndSeedTerminal(term, initialSize)` at
`src/renderer/src/hooks/use-terminal.ts:374` to attach to the remote PTY.

3. `attachAndSeedTerminal` (lines 262-303) calls `pear.broker.attachTerminal(...)` at
lines 269-275, which returns a snapshot containing `result.snapshot.screen` for the
authoritative PTY screen. If a snapshot is present, it writes that screen to the live
terminal at 279-280, then *awaits*
`predictiveEchoRef.current?.seed(result.snapshot.screen)` at line 284 to prime the
predictive-echo engine. Only after this asynchronous `seed` completes does it set
`writtenChunksRef.current = useAgentStore.getState().getAgentBuffer(projectId,
agentName!).length` at line 285 and later call `subscribeToBuffer(targetTerm)` at line
302.

4. Meanwhile, PTY output from the broker is streamed into the renderer via
`'broker:pty-chunk'` IPC in `src/main/broker.ts:1482-1495`, with the renderer appending
chunks to the PTY buffer through `appendPtyChunk` in
`src/renderer/src/stores/pty-buffer-store.ts:17-33`. `getAgentBuffer` in the agent store
(`src/renderer/src/stores/agent-store.ts:1011-1012`) simply calls `getPtyChunks`, so any
chunks that arrive between the snapshot being taken and the async `seed()` completion are
already in the buffer but have not yet been written to the terminal. Because
`writtenChunksRef.current` is then advanced to the *full* buffer length before the first
`writeFromBuffer(getPtyChunks(...))` in `subscribeToBuffer` (lines 237-255), those
in-between chunks are sliced away and never written, causing a race where PTY output that
arrives during attach is permanently dropped from the terminal view.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/renderer/src/hooks/use-terminal.ts
**Line:** 284:284
**Comment:**
	*Race Condition: Awaiting predictive-echo seeding before subscribing to buffered PTY chunks increases the attach race window and can skip output that arrives between snapshot write and buffer subscription. Seed without delaying replay-state handoff (or subscribe first) so no server output is dropped during attach.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Jun 4, 2026

CodeAnt AI finished reviewing your PR.

@willwashburn willwashburn merged commit 6964820 into main Jun 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant