How the Intercom Bridge Enables Child-to-Parent Communication in pi-subagents

The intercom bridge enables child-to-parent communication by listening for intercom tool events on the child subagent’s session, translating the generic "orchestrator" target into the child’s specific session name, and forwarding the payload to the parent orchestrator’s pipe.

In the pi-subagents framework, hierarchical agent workflows require that child subagents stream status updates and data back to their parent orchestrators. The intercom bridge facilitates this communication by acting as a bidirectional gateway that wires the child’s intercom target to the parent’s listening pipe, ensuring messages flow securely across the agent boundary.

Architecture of the Intercom Bridge

Configuration and Extension Loading

Before any messages can flow, the bridge must resolve its runtime configuration. The resolveIntercomBridge function in src/intercom/intercom-bridge.ts reads the repository-wide configuration from intercom/config.json and dynamically imports the pi-intercom extension from extensions/pi-intercom/index.ts.

If the configuration sets enabled: true and the extension exposes an intercom object, the bridge activates and becomes ready to handle traffic.

export const resolveIntercomBridge = async (
  config: IntercomBridgeConfig,
  root: string,
  logger?: Logger,
): Promise<IntercomBridgeInfo> => {
  const mode = config?.mode ?? "always";
  if (mode === "never") {
    return { active: false };
  }
  
  const configPath = config?.configPath ?? path.join(root, "intercom", "config.json");
  const configContent = readFileSync(configPath, "utf-8");
  const parsed = JSON.parse(configContent);
  
  if (!parsed.enabled) {
    return { active: false, intercomConfigEnabled: false };
  }
  
  const extension = await import(path.resolve(extensionsDir, "index.ts"));
  return { active: true, intercom: extension.intercom };
};

Wiring the Child Session

When a child subagent spawns, the runtime calls createIntercomBridge to establish the communication link. This function attaches an event listener to the child’s Session object on the designated notifyChannel (typically "intercom"). It specifically filters for messages where action equals "send" and to equals "orchestrator".

Upon capturing such a message, the bridge rewrites the target from the generic "orchestrator" string to the child’s specific childSessionName (e.g., subagent-worker-<run-id>-1), then writes the transformed payload to the parentPipe.

export const createIntercomBridge = (
  parentPipe: Pipe,
  childSession: Session,
  childSessionName: string,
  notifyChannel: string,
): Promise<void> => {
  return new Promise((resolve) => {
    const onMessage = async (msg: any) => {
      if (msg?.action === "send" && msg?.to === "orchestrator") {
        await parentPipe.write({
          tool: "intercom",
          payload: {
            action: "send",
            to: childSessionName,
            payload: msg.payload,
          },
        });
        resolve();
      }
    };
    childSession.on(notifyChannel, onMessage);
    return () => childSession.removeListener(notifyChannel, onMessage);
  });
};

Parent-Side Message Handling

On the parent side, intercomBridgeHandler maintains the incoming message stream. It resolves the bridge configuration to obtain the loaded extension’s intercom iterator, then continuously pulls messages from that stream and writes them into the parent’s pipe. This ensures the orchestrator receives every child-sent payload as a standardized intercom tool event.

export const intercomBridgeHandler = async (
  parentPipe: Pipe,
  root: string,
  logger?: Logger,
): Promise<void> => {
  const bridgeInfo = await resolveIntercomBridge({ mode: "always" }, root, logger);
  if (!bridgeInfo.active) return;

  const { intercom } = bridgeInfo;
  await asyncIterate(intercom, async (msg) => {
    await parentPipe.write({
      tool: "intercom",
      payload: msg,
    });
  });
};

Implementation in the Runtime

The bridge integrates into the agent lifecycle inside src/agent-runtime.ts. After spawning a child session, the runtime checks for the presence of a notifyChannel and immediately wires the bridge before the child begins execution. Meanwhile, the parent’s main loop starts intercomBridgeHandler to listen for any incoming child traffic.

// Inside agent-runtime.ts
if (notifyChannel && childSession && childSessionName) {
  await createIntercomBridge(parentPipe, childSession, childSessionName, notifyChannel);
}

// Setup parent-side listener
await intercomBridgeHandler(parentPipe, root, logger);

End-to-End Message Flow

  1. Child emits: The subagent calls the intercom tool with { action: "send", to: "orchestrator", payload: data }.
  2. Bridge captures: createIntercomBridge detects the event on the child’s notifyChannel.
  3. Target translation: The bridge swaps "orchestrator" for the child’s unique session identifier.
  4. Parent receives: The payload flows through parentPipe to the orchestrator as an intercom tool event.
  5. Parent processes: intercomBridgeHandler ensures the message enters the parent’s event loop for handling.

Summary

  • The intercom bridge relies on resolveIntercomBridge to dynamically load the pi-intercom extension and validate configuration.
  • Child-to-parent traffic flows through createIntercomBridge, which translates generic orchestrator targets into specific session names.
  • The parent consumes these messages via intercomBridgeHandler, which iterates the extension’s message stream and writes to the parent pipe.
  • This architecture enables asynchronous, isolated communication between hierarchical agents without shared memory.

Frequently Asked Questions

What naming convention does the intercom target use for child sessions?

Child sessions receive an intercom target string formatted as subagent-worker-<run-id>-<suffix>. The bridge preserves this identifier in the to field when forwarding messages, allowing the parent to distinguish between multiple concurrent subagents.

Can multiple child agents communicate with the parent simultaneously?

Yes. Each child subagent receives its own createIntercomBridge instance attached to its unique Session and childSessionName. The parent’s intercomBridgeHandler aggregates messages from all active children into a single stream, enabling parallel subagent orchestration.

What happens if the pi-intercom extension is not installed?

If resolveIntercomBridge cannot locate the extension at extensions/pi-intercom/index.ts or the configuration has enabled: false, the function returns { active: false }. The runtime skips bridge wiring, and child intercom calls operate as no-ops without throwing errors.

Is the child-to-parent communication synchronous or asynchronous?

The communication is fully asynchronous. createIntercomBridge returns a Promise that resolves when the first matching message is forwarded, and intercomBridgeHandler uses an async iterator to process messages without blocking the parent’s main execution loop.

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 →