Skip to content

docs(design): plan event-driven tool relay (remove relay polling latency)#5232

Draft
mmabrouk wants to merge 3 commits into
big-agentsfrom
event-driven-tool-relay
Draft

docs(design): plan event-driven tool relay (remove relay polling latency)#5232
mmabrouk wants to merge 3 commits into
big-agentsfrom
event-driven-tool-relay

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

Context

Every gateway tool call from a sandboxed harness pays a polling tax today: the in-sandbox writer sleeps in 300 ms steps waiting for the response file, and the runner discovers request files by listing the relay dir every 300 ms (backing off to 1.5 s when idle). That adds roughly 0.3 to 1.8 s of pure waiting per tool call, and on Daytona it also costs ~3 remote ls execs per second for the whole turn.

This follows the owner decision (2026-07-11, recorded in mcp-delivery-architecture/gateway-mcp-location.md): the API-hosted tool gateway is rejected, the sandbox talks only to the runner, tool calls stay on the file relay, and the relay's polling latency is addressed as its own feature. This PR is that feature's planning workspace.

What the plan proposes

Design only, no runtime code. New workspace at docs/design/agent-workflows/projects/event-driven-tool-relay/ (README, context, research with verified file:line anchors, plan, open-questions, status).

The core idea: events are wake signals only; the poll loops stay as the correctness fallback. Each await sleep(delay) becomes Promise.race([sleep(delay), watcher.wake()]), so a dead watcher degrades to today's behavior, never to a hang.

  • Hop 1 (writer waits for res.json): fs.watch in the shared relayToolCall, so Pi's extension and the future Claude MCP shim both get it. Same trick runner-side on the local backend.
  • Hop 2 (runner spots req.json on Daytona): one bounded (25 s) blocking runProcess holding a tiny node script that lists first (closes the created-before-armed race), watches, prints, exits; the runner re-issues it per window. Replaces ~180 daemon requests/min with ~2.4/min.
  • Alternatives evaluated and not picked: persistent watcher + log-follow stream (orphan and reconnect lifecycle), ACP extNotification doorbell (Pi-only, does not cover the MCP shim), tighter polling.
  • Reliability/scale analysis covers races, coalescing, watcher lifecycle, held-connection limits, runner restart, warm sessions, and the second relay writer. Expected result: per-call relay overhead from 0.3-1.8 s down to under ~150 ms on Daytona and near zero locally, with fewer daemon requests and unchanged security model.

What review is needed

This is a plan to interview, not code. Please read plan.md (five decisions, each with options and why) and open-questions.md (six questions; Daytona held-exec limits gate the default-on step). Push back on decision 2 (bounded watch exec vs persistent watcher) and decision 5 (flag shape) in particular.

https://claude.ai/code/session_01Fr4A5zjs5rsufRaWgWiUNC

@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 12, 2026 3:03am

Request Review

@mmabrouk

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4f8edbcf-cc8b-43c6-bf74-72fbbcc735e1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This documentation set defines the event-driven tool relay design, records current polling behavior and runtime constraints, specifies watcher-assisted wakeups with polling fallback, and tracks open questions, validation status, rollout planning, and next steps.

Changes

Event-driven tool relay

Layer / File(s) Summary
Relay context and documentation map
docs/design/agent-workflows/projects/event-driven-tool-relay/README.md, context.md, open-questions.md
Introduces the relay glossary, reading order, preserved contracts, reliability principles, goals, non-goals, and six unresolved design questions.
Current relay behavior and runtime constraints
docs/design/agent-workflows/projects/event-driven-tool-relay/research.md
Documents hop-1 and hop-2 polling, request/response cleanup, filesystem assumptions, Daytona and ACP constraints, and latency/request-volume baselines.
Watcher wakeup architecture
docs/design/agent-workflows/projects/event-driven-tool-relay/plan.md
Specifies wake-only watchers, local writer integration, bounded Daytona watch executions, polling fallback, configuration, reliability behavior, implementation slices, tests, rollout, and rollback.
Project status and execution tracking
docs/design/agent-workflows/projects/event-driven-tool-relay/status.md
Records project state, timeline, verified and unverified assumptions, blockers, and next steps.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the design work to plan an event-driven tool relay and reduce polling latency.
Description check ✅ Passed The description matches the changeset and explains the new design workspace and its goals in relevant detail.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch event-driven-tool-relay

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.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
docs/design/agent-workflows/projects/event-driven-tool-relay/plan.md (1)

184-197: 🩺 Stability & Availability | 🔵 Trivial

Treat Daytona capacity assumptions as rollout gates, not settled facts.

open-questions.md says concurrent-exec and proxy limits are unverified, but this section presents connection/fd usage as approximately unchanged and one held exec per turn as a safe baseline. Validate per-sandbox and per-organization concurrency, saturation behavior, and recovery before enabling the flag by default.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 90042a55-c167-49ee-8a2f-72e8e784b01e

📥 Commits

Reviewing files that changed from the base of the PR and between 8972ecc and b43de15.

📒 Files selected for processing (6)
  • docs/design/agent-workflows/projects/event-driven-tool-relay/README.md
  • docs/design/agent-workflows/projects/event-driven-tool-relay/context.md
  • docs/design/agent-workflows/projects/event-driven-tool-relay/open-questions.md
  • docs/design/agent-workflows/projects/event-driven-tool-relay/plan.md
  • docs/design/agent-workflows/projects/event-driven-tool-relay/research.md
  • docs/design/agent-workflows/projects/event-driven-tool-relay/status.md

Comment thread docs/design/agent-workflows/projects/event-driven-tool-relay/plan.md Outdated
Comment thread docs/design/agent-workflows/projects/event-driven-tool-relay/plan.md Outdated
Comment thread docs/design/agent-workflows/projects/event-driven-tool-relay/plan.md Outdated
Comment thread docs/design/agent-workflows/projects/event-driven-tool-relay/plan.md Outdated
mmabrouk added 2 commits July 11, 2026 22:49
Adopt the Codex review: atomic temp-plus-rename publication in both relay
directions (correcting the seen-set partial-file claim), a coalesced
single-flight RelayActivitySource wait contract replacing the bare promise
race, arm-watch-then-list ordering in the Daytona exec script, and a Daytona
mode where a healthy watch suspends the runner's remote polling (kept 30 s
safety poll, justified) with honest request-volume numbers. Make the
relay-client.ts/relay-protocol.ts extraction slice 0 of this project (the
in-sandbox-tool-mcp sibling consumes it), fold in the watcher-hardening list,
rename the flags per hop with a validated clamped window, and merge the
expanded test list. Address the four CodeRabbit comments (held-request abort,
argv-safe script args, window clamping, three-way latency accounting with
capacity assumptions as rollout gates) and the owner's framing points (writer
coverage stated up front; rejected alternatives unmistakably rejected).

Claude-Session: https://claude.ai/code/session_0127AM79khCdvD2b8BG2joZL
@mmabrouk mmabrouk force-pushed the event-driven-tool-relay branch from b43de15 to 633df7b Compare July 11, 2026 21:15
@mmabrouk

Copy link
Copy Markdown
Member Author

Review round addressed in 633df7b. What changed, per source:

Codex P0s (all adopted)

  • Atomic publication (new decision 2): both directions publish via <name>.tmp.<nonce> plus a same-directory rename. The final file contract is unchanged; suffix filters never match temp names. The plan now states plainly that the earlier claim ("the seen set handles partial files") was wrong; it only dedups filenames. Reader-side JSON retry is recorded as the rejected weaker fallback. New open question: whether the daemon's moveFs is rename(2)-atomic; if not, RelayHost's rename capability uses a shell mv exec.
  • Wake contract (new decision 3): the bare Promise.race([sleep, watcher.wake()]) is gone. The seam is a coalesced single-flight RelayActivitySource with wait({timeoutMs, signal}) -> "activity" | "timeout" | "closed" plus close(), with the full invariant list (one runProcess per relay, sticky coalesced notification, zero listener growth across timer wins, events between scan and wait stay observable, close() wakes and prevents rearming, errors logged once). The watch script now arms the watch first, then lists, then waits on the sticky signal; the plan says why the old list-then-watch order was racy.
  • Request volume (decision 4 rewrite): a healthy watch now suspends the runner's remote ls polling. The in-sandbox script carries its own periodic readdir as the in-window fallback; the runner keeps a hard outer bound (window + 5 s grace, with an abort) and demotes the turn to the classic poll loop on repeated failure with jittered backoff. Decision made: keep an ultra-slow 30 s runner safety poll while the watch is healthy. It is independent of every watch-subsystem layer (wedged script, paused sandbox, lying daemon), bounds worst-case pickup at 30 s absolutely, and costs 2 requests/min next to 2.4 watch re-issues/min. Numbers corrected everywhere: ~200 req/min active today -> ~4.4 req/min healthy, roughly 45x fewer active and 9x fewer than the idle floor. The old ~75x claim was wrong for the old design and the plan says so.

Codex P1s

  • Module placement: tools/relay-client.ts (publication, wait, timeout, abort, cleanup) plus tools/relay-protocol.ts (suffixes, types, id sanitization, bundle-safe) are now slice 0 of this project; docs(design): plan in-sandbox platform-tool MCP (Claude/Codex tools on Daytona, unified with Pi) #5234 consumes them instead of creating them (ownership settled with that workspace; its plan.md:117/:269 references are being corrected on its side). The runner-side wake lives behind RelayHost.waitForActivity; no shared wake helper sits in server-heavy relay.ts.
  • Watcher hardening: the full list is folded into "Watch exec details" (wake on every event regardless of eventType/filename, error handling, argv-passed relay dir, close on every exit, completion status over stdout, internal timer + daemon timeout = window + grace, jittered backoff, Node fs.watch portability and the recreated-directory inode gotcha with a QA cell).
  • Flags (decision 7): per hop, renamed: AGENTA_AGENT_TOOLS_RELAY_RESPONSE_WATCH_ENABLED, AGENTA_AGENT_TOOLS_RELAY_REMOTE_WATCH_ENABLED, AGENTA_AGENT_TOOLS_RELAY_REMOTE_WATCH_WINDOW_MS (validated, clamped, jittered). Idle backoff survives only in fallback poll mode; local hops keep a cheap safety timer.
  • Tests: the Codex list is merged into the test plan (chunked publication, rename on both hosts, boundary events, listener-growth and max-one-exec assertions, stop() during a held window, daemon restart / proxy disconnect / sandbox pause / directory replacement, node missing, ENOSPC/EMFILE, batch load, and the request/socket/exec/latency metric set). Deterministic tests prove ordering; live QA records distributions; no latency asserts in CI.

CodeRabbit (4 inline comments, replied on each thread)

  • plan.md~62 held-request cancellation: close() aborts the in-flight exec request, and every window carries an AbortSignal at window + grace, so relay stop or a stalled proxy cannot pin the turn.
  • plan.md~85 script injection: the relay dir is passed as argv, never interpolated; a regression test covers quotes, whitespace, metacharacters, and newlines.
  • plan.md~146 window validation: parsed, invalid falls back to 25000 with one warning, clamped to [5000, 120000], ~20% jitter per window.
  • plan.md~223 latency accounting: three measures defined (wake latency, handler pickup, full relay completion); the <150 ms target is median handler pickup including the post-wake list+read round-trips, and it plus the Daytona capacity assumptions are rollout gates, not facts.

Owner framing points

  • The README and the plan's opening now state up front who writes the relay: Pi's extension and local Claude (through the loopback MCP handler and the same dispatch) today, the docs(design): plan in-sandbox platform-tool MCP (Claude/Codex tools on Daytona, unified with Pi) #5234 stdio shim tomorrow, and why the watch is writer-agnostic (it watches the directory).
  • Rejected options moved into an explicit "Alternatives considered and rejected" subsection, and the ACP doorbell entry leads with why it cannot serve Claude (separate process, no ACP session).

open-questions.md marks what the review answered (flag naming, backoff fate, sequencing, default-on timing) and adds the new items (Daytona moveFs atomicity, capacity gates, the stale references in orchestration.md, and the orphaned-request residue ownership call).

https://claude.ai/code/session_0127AM79khCdvD2b8BG2joZL

mmabrouk added a commit that referenced this pull request Jul 11, 2026
…relay modules, shrink v1

Fold the Codex xhigh review into the in-sandbox tool MCP plan. Gate A2 on a new
slice 0 restart spike (session/load after a VM stop is adapter behavior, not
correct by construction; orphan exit weakened to an expectation). Hand the
relay-client/relay-protocol extraction to PR #5232 slice 0 and consume it here,
reversing the earlier ordering. Cut the standalone transport-neutral mcp-handler
slice and rewrite the unification section honestly (Pi never speaks MCP; the real
sharing is the relay client and file protocol). Decide specs delivery now: a file,
not an unbounded env variable. Move client tools, Codex-on-Daytona, snapshot bake,
Pi-as-MCP-client, watch adoption, and mandatory replay capture to explicit
follow-ups. Add the missing warm-reuse edges (session/load, sanitized-ID collision,
bundle skew, partial visibility via #5232's atomic rename). Make the internal/user
stdio separation structural and fix naming (tool-mcp-stdio.ts,
internal-tool-mcp-handler.ts, helpers under engines/sandbox_agent/, ACP entry
shapes out of mcp-bridge.ts).

Claude-Session: https://claude.ai/code/session_0127AM79khCdvD2b8BG2joZL
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