Skip to content

feat: multi-workspace runtime support - #519

Merged
khaliqgant merged 12 commits into
mainfrom
feature/multi-workspace-impl
Mar 10, 2026
Merged

feat: multi-workspace runtime support#519
khaliqgant merged 12 commits into
mainfrom
feature/multi-workspace-impl

Conversation

@khaliqgant

@khaliqgantkhaliqgant commented Mar 9, 2026

Copy link
Copy Markdown
Member

Implements multi-workspace support for the relay runtime, allowing one agent process to connect to multiple Relaycast workspaces simultaneously.

Changes

Auth & Session Model (~565 lines)

  • auth.rs: CredentialCacheCredentialSet with memberships[] and default_workspace_id
  • New multi_workspace.rs: MultiWorkspaceSession managing N WebSocket connections with merged event fan-in
  • Legacy RELAY_API_KEY auto-upgrades to single-entry membership set
  • New RELAY_WORKSPACES_JSON env var for multi-workspace bootstrap

Event Routing (~100 lines)

  • types.rs: workspace_id and workspace_alias added to InboundRelayEvent, BrokerCommandEvent, InjectRequest
  • message_bridge.rs: accepts workspace context, emits workspace-scoped events
  • routing.rs: routes by (workspace_id, target) instead of just target

Runtime Flows (~700 lines)

  • main.rs: merged receive loop, workspace-aware self-echo filtering, history/replay
  • listen_api.rs: /api/send accepts workspaceId/workspaceAlias, /api/config reports memberships
  • wrap.rs: workspace-qualified PTY injection when multi-workspace active
  • spawner.rs: passes RELAY_WORKSPACES_JSON and RELAY_DEFAULT_WORKSPACE to child processes

Backwards Compatibility

  • Legacy single-workspace env vars still work
  • Old cache files auto-promote to v2 CredentialSet
  • /api/send without workspace valid when single membership or default set

Compiles clean (cargo check passes).

See SPEC.md on feature/multi-workspace-support branch for full architecture details.


Open with Devin

@devin-ai-integrationdevin-ai-integrationBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment threadsrc/wrap.rs Outdated
Comment on lines +1001 to +1002
let _ = pty.write_all(b"
");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Retry injection writes newline+spaces instead of carriage return to PTY

In the retry injection path, the old code pty.write_all(b"\r") (carriage return / Enter key to submit the injected text) was accidentally replaced with a raw multi-line byte string b"\n " (a newline followed by 8 spaces). This means the re-injected message will never be submitted to the CLI — the PTY receives garbage whitespace instead of an Enter keystroke. The original code at src/wrap.rs before this PR used let _ = pty.write_all(b"\r"); which correctly pressed Enter. The non-retry injection path at src/wrap.rs:916 still correctly uses pty.write_all(b"\r"), confirming this was a reformatting accident during the multi-workspace refactor.

Suggested change
let _ = pty.write_all(b"
");
let _ = pty.write_all(b"\r");
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

The retry re-injection code was sending a literal newline + spaces
instead of \r (carriage return) after writing the injection to the PTY.
This would cause retry deliveries to fail silently in wrap mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

khaliqgantand others added 2 commits March 9, 2026 18:51
- BUG-2: Use workspace-specific HTTP client and ws_control_tx for broker
spawn/release instead of always using the default workspace's client
- P1: Add workspace_id filtering to routing resolution so messages from
workspace A's #general don't cross-deliver to workspace B's workers
- Add workspace_id to RoutingWorker and WorkerHandle, with backwards
compat (None matches all workspaces for legacy/SDK-spawned workers)
- Update agent-relay-snippet.md with multi-workspace section
- Remove unnecessary #[allow(dead_code)] on DeliveryOutcome::Failed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

khaliqgantand others added 3 commits March 10, 2026 10:45
The constructor requires all 8 parameters for initialization and
does not benefit from a builder pattern at this stage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The dedup key format for incoming WS events was changed to
`workspace_id:event_id`, but MCP self-echo pre-seeding still inserted
bare message IDs. This mismatch caused duplicate echo messages because
the pre-seeded bare ID never matched the scoped incoming key.
Pre-seed dedup entries for all workspaces so the scoped keys match on
arrival.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…orward workspace env vars in MCP snippets
Gap 1: When a configured default_workspace_id is not found in the lookup
table, the error now says "workspace_not_found:" instead of
"ambiguous_workspace:" (both in relay_send routing and protocol frame
handling). The "ambiguous_workspace:" error remains for the fallback case
where multiple workspaces exist with no default configured.
Gap 2: Forward RELAY_WORKSPACES_JSON and RELAY_DEFAULT_WORKSPACE env vars
through MCP configuration snippets for all supported CLIs (claude, codex,
gemini/droid, opencode, cursor) so spawned child agents inherit workspace
context and can connect to the correct workspaces.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

RELAY_WORKSPACES_JSON contains JSON with inner double quotes that
break TOML basic-string parsing in codex --config args. Escape
backslashes and quotes before interpolation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@khaliqgant
khaliqgant merged commit 7712600 into mainMar 10, 2026
38 of 39 checks passed
@khaliqgant
khaliqgant deleted the feature/multi-workspace-impl branch March 10, 2026 10:50
khaliqgant added a commit that referenced this pull request Mar 25, 2026
* feat: add multi-workspace auth plumbing
* feat: wire multi-workspace runtime flows
* chore: record multi-workspace implementation trail
* fix: restore carriage return in wrap retry PTY injection
The retry re-injection code was sending a literal newline + spaces
instead of \r (carriage return) after writing the injection to the PTY.
This would cause retry deliveries to fail silently in wrap mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address multi-workspace code review bugs from PR #519
- BUG-2: Use workspace-specific HTTP client and ws_control_tx for broker
spawn/release instead of always using the default workspace's client
- P1: Add workspace_id filtering to routing resolution so messages from
workspace A's #general don't cross-deliver to workspace B's workers
- Add workspace_id to RoutingWorker and WorkerHandle, with backwards
compat (None matches all workspaces for legacy/SDK-spawned workers)
- Update agent-relay-snippet.md with multi-workspace section
- Remove unnecessary #[allow(dead_code)] on DeliveryOutcome::Failed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: auto-format Rust code with cargo fmt
* fix: allow clippy too_many_arguments on MultiWorkspaceSession::new
The constructor requires all 8 parameters for initialization and
does not benefit from a builder pattern at this stage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Align SDK send payload with workspace fields
* fix: use workspace-scoped dedup keys for MCP self-echo pre-seeding
The dedup key format for incoming WS events was changed to
`workspace_id:event_id`, but MCP self-echo pre-seeding still inserted
bare message IDs. This mismatch caused duplicate echo messages because
the pre-seeded bare ID never matched the scoped incoming key.
Pre-seed dedup entries for all workspaces so the scoped keys match on
arrival.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct error message for default workspace lookup failure and forward workspace env vars in MCP snippets
Gap 1: When a configured default_workspace_id is not found in the lookup
table, the error now says "workspace_not_found:" instead of
"ambiguous_workspace:" (both in relay_send routing and protocol frame
handling). The "ambiguous_workspace:" error remains for the fallback case
where multiple workspaces exist with no default configured.
Gap 2: Forward RELAY_WORKSPACES_JSON and RELAY_DEFAULT_WORKSPACE env vars
through MCP configuration snippets for all supported CLIs (claude, codex,
gemini/droid, opencode, cursor) so spawned child agents inherit workspace
context and can connect to the correct workspaces.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: escape TOML string values for codex --config workspace env vars
RELAY_WORKSPACES_JSON contains JSON with inner double quotes that
break TOML basic-string parsing in codex --config args. Escape
backslashes and quotes before interpolation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
khaliqgant added a commit that referenced this pull request Mar 25, 2026
- BUG-2: Use workspace-specific HTTP client and ws_control_tx for broker
spawn/release instead of always using the default workspace's client
- P1: Add workspace_id filtering to routing resolution so messages from
workspace A's #general don't cross-deliver to workspace B's workers
- Add workspace_id to RoutingWorker and WorkerHandle, with backwards
compat (None matches all workspaces for legacy/SDK-spawned workers)
- Update agent-relay-snippet.md with multi-workspace section
- Remove unnecessary #[allow(dead_code)] on DeliveryOutcome::Failed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for freeto 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

@khaliqgant