Skip to content

Clean up paired bridge messaging#56

Merged
axeldelafosse merged 2 commits intomainfrom
codex/bridge-send-message-cleanup
Apr 1, 2026
Merged

Clean up paired bridge messaging#56
axeldelafosse merged 2 commits intomainfrom
codex/bridge-send-message-cleanup

Conversation

@axeldelafosse
Copy link
Copy Markdown
Owner

Summary

This change cleans up paired bridge messaging in interactive tmux mode.

It renames the user-facing bridge tool from send_to_agent to send_message, updates the paired and tmux guidance so the prompts teach that contract consistently, replaces Claude-side bridge polling with fs.watch plus a bounded fallback sweep, and wraps Codex-bound peer messages in a structured <loop-bridge ...> text envelope with stable metadata.

Why

The bridge contract was still teaching transport-flavored behavior instead of the user action we actually want agents to take. Claude delivery also depended on a fixed polling loop, and Codex inbound messages were less structurally distinct than Claude channel messages.

Impact

Paired prompts, bridge config, and tests now consistently use send_message. The old send_to_agent name is rejected with a clear rename hint. Claude-side peer delivery now flushes on file events instead of relying on a 500 ms poll loop, and Codex receives bridge messages with explicit peer-message structure.

Root Cause

The bridge contract had drifted toward transport-specific naming and polling-based delivery. While adding the new Codex wrapper, normalization also needed an extra trim step so wrapped and unwrapped forms collapse to the same value and bounce detection still works.

Validation

  • bun run fix
  • bun run check
  • bun test

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 renames the send_to_agent tool to send_message and introduces a more robust message delivery mechanism using a structured <loop-bridge> XML-like envelope that includes message IDs. It also replaces the polling-based message delivery for Claude with a file-system watcher (fs.watch) and a fallback sweep timer to improve responsiveness and efficiency. Feedback focuses on improving the robustness of the new delivery logic, specifically suggesting a debounce for the file watcher to prevent redundant processing during rapid file writes, more specific regex for tag stripping to avoid accidental matches, and ensuring the fallback timer is cleared before rescheduling to prevent overlapping timers.

Comment on lines +538 to 551
const scheduleClaudeSweep = (): void => {
if (!(source === "claude" && channelReady) || closed || fallbackSweep) {
return;
}
fallbackSweep = setTimeout(() => {
fallbackSweep = undefined;
if (closed) {
return;
}
await new Promise((resolve) => {
setTimeout(resolve, CHANNEL_POLL_DELAY_MS);
});
}
triggerClaudeFlush();
scheduleClaudeSweep();
}, CLAUDE_CHANNEL_FALLBACK_SWEEP_MS);
fallbackSweep.unref?.();
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The fallback sweep logic could potentially lead to multiple overlapping timers if scheduleClaudeSweep is called concurrently before fallbackSweep is assigned. While the current usage in notifications/initialized seems safe, adding a check to ensure any existing timer is cleared before scheduling a new one would be more robust.

Comment on lines +556 to +561
bridgeWatcher = watch(runDir, (_eventType, filename) => {
if (!isBridgeWatchEvent(runDir, filename)) {
return;
}
await handleBridgeRequest(runDir, source, request);
await queueClaudeFlush();
};
requestQueue = requestQueue.then(handleRequest, handleRequest);
},
() => {
closed = true;
triggerClaudeFlush();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The fs.watch callback is triggered on every file system event in the runDir. Since triggerClaudeFlush initiates a promise chain that reads the entire bridge inbox, frequent events (e.g., multiple rapid writes to the bridge file) could lead to redundant processing. Consider adding a small debounce to triggerClaudeFlush to improve efficiency during bursts of activity.

@@ -1,18 +1,34 @@
import type { Agent } from "./types";

const BRIDGE_TAG_RE = /<\/?loop-bridge(?:\s+[^>]*)?>/gi;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The BRIDGE_TAG_RE regex is quite permissive with [^\u003e]*. While it works for the current structured envelope, it might accidentally match and strip other tag-like strings in the message body if they happen to start with loop-bridge. A more specific regex matching only the expected attributes (source and message_id) would be safer.

@axeldelafosse axeldelafosse marked this pull request as ready for review April 1, 2026 04:00
@axeldelafosse axeldelafosse merged commit 4b470f5 into main Apr 1, 2026
2 checks passed
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.

1 participant