How the RPC Layer Communicates with AgentSession in Pi-Web

The RPC layer in pi-web uses a thin AgentSessionWrapper class in lib/rpc-manager.ts to bridge HTTP JSON commands from the UI to the underlying Pi AgentSession, dispatching SDK methods like prompt and executeBash while re-emitting events back through SSE streams.

The pi-web repository implements a remote procedure call (RPC) mechanism that allows the browser UI to control AI agent sessions over HTTP. This architecture centers on a wrapper pattern that translates JSON commands into native SDK calls on the AgentSession instance, enabling real-time interaction with the Pi agent backend.

RPC Architecture Overview

The communication model follows a simple command-object protocol. Client-side code invokes sendAgentCommand from lib/agent-client.ts, which POSTs JSON payloads to /api/agent/[sessionId]. The server-side RPC manager maintains a registry of AgentSessionWrapper instances that mediate between the HTTP layer and the core AgentSessionLike interface provided by the Pi SDK.

The Communication Flow

The end-to-end RPC communication follows five distinct steps:

  1. HTTP Request Initiation – The client calls sendAgentCommand (lines 10‑28 in lib/agent-client.ts) to POST a JSON command object to the Next.js API route /api/agent/[sessionId].

  2. Session Resolution – The API handler retrieves the corresponding wrapper instance via getRpcSession(sessionId) (lines 75‑77 in lib/rpc-manager.ts).

  3. Command Dispatch – The wrapper’s send(command) method (lines 104‑226 in lib/rpc-manager.ts) receives the payload, validates the command type, and forwards the request to the underlying AgentSessionLike (inner) using SDK methods such as prompt, abort, navigateTree, or executeBash.

  4. Event Propagation – The wrapper subscribes to the session’s internal event stream through inner.subscribe (lines 43‑55) and re-emits those events to any listeners attached by the UI, typically via Server-Sent Events (SSE).

  5. State Broadcasting – After commands that modify execution status, notifyRunningChange() (lines 14‑22) recomputes active sessions and pushes updates to global listeners, keeping the sidebar UI synchronized.

Core Implementation Files

AgentSessionWrapper (lib/rpc-manager.ts)

The AgentSessionWrapper class (lines 8‑22) serves as the primary intermediary. It stores a reference to the inner AgentSessionLike instance and exposes a unified send interface. The wrapper handles three critical responsibilities:

  • Subscription Management – The start() method (lines 43‑55) registers with inner.subscribe to capture agent events like agent_start and agent_end, forwarding them to UI listeners.

  • Command Routing – The send(command) method (lines 104‑226) implements a switch statement on command.type, handling variants such as "prompt", "fork", "bash", and "navigate_tree".

  • Lifecycle Coordination – notifyRunningChange() broadcasts running-state updates whenever sessions start or terminate.

Client-Side Command Helper (lib/agent-client.ts)

Front-end components interact with the RPC layer through sendAgentCommand (lines 10‑28). This helper constructs the POST request to /api/agent/[id] with proper JSON serialization and error handling, abstracting the HTTP transport details from React components.

HTTP Route Handling

The Next.js dynamic route app/api/agent/[id]/route.ts receives incoming POST requests, extracts the session ID from the path parameter, and delegates to the RPC manager. It uses getRpcSession(id) to retrieve the active wrapper and calls wrapper.send(body) to execute the command.

Command Dispatch Examples

Sending a Prompt Command

When the UI submits a user message, the RPC layer translates this into a prompt call on the underlying session:

import { sendAgentCommand } from '@/lib/agent-client';

async function askAssistant(sessionId: string, text: string) {
  await sendAgentCommand(sessionId, {
    type: 'prompt',
    message: text,
    streamingBehavior: 'followUp',
  });
}

The wrapper’s send method detects type === "prompt" and invokes this.inner.prompt(command.message, { streamingBehavior, source: "rpc" }) at lines 119‑124 of lib/rpc-manager.ts.

Forking a Session

Forking creates a new session branched from a specific conversation entry:

const result = await sendAgentCommand(sessionId, {
  type: 'fork',
  entryId: entryId,
});

if (!result.cancelled) {
  const newId = result.newSessionId;
}

This triggers the logic at lines 84‑118 in lib/rpc-manager.ts, where the wrapper instantiates a new SessionManager, writes the forked conversation to a .jsonl file, caches the path, and destroys the old wrapper reference.

Executing Bash Commands

For terminal operations, the RPC layer supports direct bash execution:

await sendAgentCommand(sessionId, {
  type: 'bash',
  command: 'ls -la',
  excludeFromContext: false,
});

The wrapper handles this at lines 72‑90 by calling inner.executeBash and persisting a “bash-only” session record when necessary.

Event Propagation and State Broadcasting

Beyond command dispatch, the RPC layer maintains bidirectional communication through event streaming. The AgentSessionWrapper subscribes to the inner session’s observable at initialization (lines 43‑55), capturing SDK events and re-emitting them to the UI layer. This enables real-time updates in the sidebar without polling.

The notifyRunningChange() function (lines 14‑22) maintains a global registry of active session IDs. When sessions transition between idle and running states, this function pushes updates to registered listeners, ensuring the interface accurately reflects which agents are currently processing commands.

Summary

  • The RPC layer uses lib/rpc-manager.ts to manage session wrappers that bridge HTTP and the Pi SDK.
  • Command dispatching occurs through AgentSessionWrapper.send(), which routes JSON commands to specific SDK methods like prompt and executeBash.
  • Session lifecycle is handled by startRpcSession and getRpcSession, ensuring each HTTP request targets the correct agent instance.
  • Real-time updates flow back to the UI via event subscription and notifyRunningChange() broadcasting.
  • Client abstraction is provided by sendAgentCommand in lib/agent-client.ts, simplifying front-end integration.

Frequently Asked Questions

What is the role of AgentSessionWrapper in pi-web's RPC layer?

AgentSessionWrapper acts as an adapter between the HTTP-based RPC protocol and the Pi SDK’s AgentSessionLike interface. It receives JSON commands from the Next.js API routes, translates them into SDK method calls, and forwards events from the agent back to the UI. This wrapper pattern isolates transport concerns from session logic, allowing the core agent implementation to remain agnostic of HTTP specifics.

How does the UI send commands to the AgentSession?

The UI imports sendAgentCommand from lib/agent-client.ts and invokes it with a session ID and command object. This helper POSTs the payload to /api/agent/[sessionId], where the server retrieves the corresponding AgentSessionWrapper via getRpcSession and executes the command. The asynchronous flow supports both immediate responses and streaming updates through SSE connections.

What happens when a fork command is executed via RPC?

When the RPC layer receives a type: 'fork' command, the AgentSessionWrapper creates a new session branched from the specified entry ID. It instantiates a fresh SessionManager, writes the conversation history to a new .jsonl file, and returns the new session ID to the client. The old wrapper is destroyed to prevent memory leaks, while the new session becomes available for subsequent commands under its own ID.

How does the RPC layer handle real-time events from the agent?

During initialization, AgentSessionWrapper calls inner.subscribe to register listeners for SDK events such as agent_start and agent_end. When the underlying session emits events, the wrapper re-emits them to registered UI listeners, typically consumed through the SSE stream in hooks/useAgentSession.ts. Additionally, notifyRunningChange() broadcasts global state updates whenever sessions start or complete execution, keeping the sidebar status indicators synchronized.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →