Skip to content

codex/core-simplification: workspace setup and core surface simplification#1

Open
willwashburn wants to merge 5 commits into
mainfrom
codex/core-simplification
Open

codex/core-simplification: workspace setup and core surface simplification#1
willwashburn wants to merge 5 commits into
mainfrom
codex/core-simplification

Conversation

@willwashburn
Copy link
Copy Markdown
Member

@willwashburn willwashburn commented May 29, 2026

Summary

Carries over the codex/core-simplification work from the relay monorepo:

  • Simplify Agent Relay core surfaces
  • Keep OpenClaw adapter
  • Make workspace setup first-class

Notes

The chore: extract to standalone repo and ci: add publish and test workflows commits on this branch overlap with what was applied directly to main during the repo extraction — expect a merge conflict on those two commits that will need to be resolved on merge.

🤖 Generated with Claude Code


Summary by cubic

Switches the OpenClaw adapter to a workspace‑first Agent Relay flow and updates naming from “Relaycast” to “Agent Relay,” with spawning now managed via @agent-relay/sdk. CLI/docs default to creating a workspace; config prefers RELAY_WORKSPACE_KEY while keeping RELAY_API_KEY as a fallback.

  • New Features

    • Workspace-first setup: relay-openclaw setup creates or joins a workspace and starts the gateway; README/SKILL updated. Setup reuses saved config when re-run with the same name.
    • Driver-managed spawning: added bridge/spawn-from-env.mjs; Docker/Process spawns use AgentRelayClient.spawn* and set RELAY_WORKSPACE_KEY (also mirrored to RELAY_API_KEY).
    • Consistent naming and config: “Agent Relay” across CLI/docs; gateway reads RELAY_WORKSPACE_KEY first and falls back to RELAY_API_KEY.
  • Migration

    • Existing RELAY_API_KEY keeps working; setup now writes RELAY_WORKSPACE_KEY and retains RELAY_API_KEY as an alias.
    • Recommended flow: npx -y @agent-relay/openclaw setup --name <claw>; pass a shared rk_live_... only to join an existing workspace.
    • Programmatic users should use @agent-relay/sdk (AgentRelayClient.spawn* or spawnFromEnv); SpawnOptions.relayApiKey is deprecated in favor of workspaceKey.

Written for commit 7435523. Summary will update on new commits.

Review in cubic

willwashburn and others added 4 commits May 27, 2026 13:31
- Expand tsconfig.json from monorepo extends to self-contained compiler options
- Update package.json repository URL to standalone repo
- Fix test scripts to run vitest directly (no longer in monorepo)
- Add typescript and vitest to devDependencies
- Add .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

Review Change Stack

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

The PR consolidates authentication terminology and architecture across the OpenClaw bridge. It introduces a new driver-managed spawn-from-env.mjs executable, updates configuration and type signatures to use workspaceKey as the canonical parameter, refactors both Docker and Process spawn providers to enforce key presence and wire consistent environment variables, and rebrands product messaging from Relaycast to Agent Relay throughout documentation and CLI surfaces.

Changes

Workspace Key Consolidation and Spawn Architecture Migration

Layer / File(s) Summary
Workspace Key Type Contracts
src/spawn/types.ts, src/types.ts
SpawnOptions adds optional workspaceKey and deprecates relayApiKey. JSDoc comments in GatewayConfig and WorkspaceEntry updated to describe workspace key terminology.
New Bridge CLI Entrypoint
bridge/spawn-from-env.mjs
Thin Node.js CLI that conditionally maps RELAY_WORKSPACE_KEY to RELAY_API_KEY when API key is absent, awaits spawnFromEnv() from SDK, and installs top-level error handler.
Configuration Loading and Persistence
src/config.ts
loadGatewayConfig() now reads RELAY_WORKSPACE_KEY first with fallback to RELAY_API_KEY. saveGatewayConfig() persists both RELAY_WORKSPACE_KEY and RELAY_API_KEY as compatibility alias.
Setup Flow with Workspace Key Resolution
src/setup.ts
SetupOptions and SetupResult interfaces updated to prefer workspaceKey. Setup implementation loads saved gateway config, resolves workspace key from options or saved config with precedence fallback, updates mcporter and gateway environment wiring to use RELAY_WORKSPACE_KEY, and returns workspaceKey in success payload. Fallback documentation rebranded from Relaycast to Agent Relay.
Docker Spawn Provider with Driver-Managed Bridge
src/spawn/docker.ts
Updated to resolve and symlink bridge.mjs and spawn-from-env.mjs into /tmp, then invoke node /tmp/openclaw-spawn-from-env.mjs as PID1. spawn() now derives workspaceKey from options with validation and wires both RELAY_WORKSPACE_KEY and RELAY_API_KEY environment variables.
Process Spawn Provider with AgentRelayClient
src/spawn/process.ts
Refactored to use AgentRelayClient from SDK. spawn() derives workspaceKey from options with validation and uses AgentRelayClient.spawn() for broker/bridge spawning. Updated environment wiring for both RELAY_WORKSPACE_KEY and RELAY_API_KEY.
Gateway /spawn Endpoint Integration
src/gateway.ts
/spawn handler now passes workspaceKey instead of relayApiKey in SpawnOptions.
CLI Commands with Workspace Key Arguments
src/cli.ts
runSetup, runStatus, and runAddWorkspace updated to accept workspaceKey as primary positional argument. Help text and output labels changed from API key to workspace key terminology.
Documentation and Product Rebranding
README.md, skill/SKILL.md
Product rebranded from Relaycast to Agent Relay. Setup and join instructions updated to use rk_live_SHARED_WORKSPACE_KEY terminology. Troubleshooting commands changed to use setup --name without explicit workspace key. Environment variable references updated throughout.
Build Configuration and Project Metadata
tsconfig.json, package.json, .gitignore
tsconfig.json inlines explicit compiler options. package.json updated with Agent Relay description and repository URL. typescript 5.8.0 added and vitest bumped to 3.2.4. .gitignore populated with node_modules/, dist/, *.tsbuildinfo, and .env* exclusions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A bridge reborn, now branded true,
Keys consolidated, old names through and through,
From Relaycast past to Relay's dawn,
Configuration flows, authentication drawn,
With driver-managed spawn, the runtime takes flight! 🚀

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: workspace setup simplification and core surface simplification for Agent Relay, which aligns with the substantial refactoring across CLI, setup, spawning, and documentation.
Description check ✅ Passed The description comprehensively explains the PR's intent to carry over core-simplification work, simplify Agent Relay surfaces, and make workspace setup first-class, directly relating to the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/core-simplification

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
bridge/spawn-from-env.mjs (1)

41-47: ⚡ Quick win

Consider adding a timeout for the agentExited event wait.

The script waits indefinitely for an agentExited event matching the agent name. If the agent fails to exit cleanly (crashes, hangs, etc.), this could cause the spawn script to hang forever without any feedback.

🔄 Suggested improvement with timeout
-    await new Promise((resolve) => {
-      client.addListener('agentExited', (event) => {
-        if (event.name === agent.name) {
-          resolve();
-        }
-      });
-    });
+    await new Promise((resolve, reject) => {
+      const timeout = setTimeout(() => {
+        reject(new Error(`Agent ${agent.name} did not exit within timeout`));
+      }, 300_000); // 5 minutes
+
+      client.addListener('agentExited', (event) => {
+        if (event.name === agent.name) {
+          clearTimeout(timeout);
+          resolve();
+        }
+      });
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bridge/spawn-from-env.mjs` around lines 41 - 47, The current await new
Promise waiting on client.addListener('agentExited', ...) can hang indefinitely;
update the promise in spawn-from-env.mjs to implement a timeout: register the
listener for 'agentExited' (matching agent.name) and also start a timer (e.g.,
setTimeout) that rejects or resolves with an error after a configurable
interval, and ensure you clear the timer and remove the listener in both the
success and timeout paths so resources are cleaned up; use the existing symbols
client.addListener, 'agentExited', agent.name, resolve/reject, and
clearTimeout/clearListener to locate and modify the code.
src/spawn/types.ts (1)

4-7: ⚡ Quick win

Fix the concern: spawn providers already validate key presence
Both DockerSpawnProvider and ProcessSpawnProvider derive workspaceKey as options.workspaceKey ?? options.relayApiKey and immediately throw if it’s missing (src/spawn/docker.ts lines ~163-169, src/spawn/process.ts lines ~63-66), so the “neither key set” case won’t fail later unpredictably. Optional: encode “at least one of workspaceKey or deprecated relayApiKey” in SpawnOptions’ type to catch this earlier.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/spawn/types.ts` around lines 4 - 7, SpawnOptions currently marks
workspaceKey and deprecated relayApiKey as optional but runtime code in
DockerSpawnProvider and ProcessSpawnProvider already derives and throws if
neither is provided; update the type to enforce "at least one present" at
compile time by replacing the two optional fields with a union that requires one
key (e.g. { workspaceKey: string; relayApiKey?: never } | { workspaceKey?:
never; relayApiKey: string }) and keep the relayApiKey marked deprecated in the
comment; reference the SpawnOptions type and the DockerSpawnProvider and
ProcessSpawnProvider derivation logic so future callers get a TS error instead
of relying solely on runtime throws.
src/spawn/docker.ts (1)

133-133: 💤 Low value

Remove unused variable r.

Line 133 creates a require function via m.createRequire() but it's never used in the subsequent code. The script continues to use the global require object.

♻️ Proposed cleanup
       'node -e "' +
         "const p = require('path');" +
         "const m = require('module');" +
-        "const r = m.createRequire(require.resolve('`@agent-relay/openclaw/package.json`'));" +
         "const dir = p.dirname(require.resolve('`@agent-relay/openclaw/package.json`'));" +
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/spawn/docker.ts` at line 133, Remove the unused local variable by
deleting the unused assignment "const r =
m.createRequire(require.resolve('`@agent-relay/openclaw/package.json`'));" so the
module.createRequire call isn't stored to an unused symbol; if you intended to
use an alternate require, replace subsequent uses of the global require with "r"
(or rename it to a meaningful identifier) and update calls accordingly,
otherwise simply remove the "r" declaration to avoid the unused-variable
warning.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@bridge/spawn-from-env.mjs`:
- Around line 41-47: The current await new Promise waiting on
client.addListener('agentExited', ...) can hang indefinitely; update the promise
in spawn-from-env.mjs to implement a timeout: register the listener for
'agentExited' (matching agent.name) and also start a timer (e.g., setTimeout)
that rejects or resolves with an error after a configurable interval, and ensure
you clear the timer and remove the listener in both the success and timeout
paths so resources are cleaned up; use the existing symbols client.addListener,
'agentExited', agent.name, resolve/reject, and clearTimeout/clearListener to
locate and modify the code.

In `@src/spawn/docker.ts`:
- Line 133: Remove the unused local variable by deleting the unused assignment
"const r =
m.createRequire(require.resolve('`@agent-relay/openclaw/package.json`'));" so the
module.createRequire call isn't stored to an unused symbol; if you intended to
use an alternate require, replace subsequent uses of the global require with "r"
(or rename it to a meaningful identifier) and update calls accordingly,
otherwise simply remove the "r" declaration to avoid the unused-variable
warning.

In `@src/spawn/types.ts`:
- Around line 4-7: SpawnOptions currently marks workspaceKey and deprecated
relayApiKey as optional but runtime code in DockerSpawnProvider and
ProcessSpawnProvider already derives and throws if neither is provided; update
the type to enforce "at least one present" at compile time by replacing the two
optional fields with a union that requires one key (e.g. { workspaceKey: string;
relayApiKey?: never } | { workspaceKey?: never; relayApiKey: string }) and keep
the relayApiKey marked deprecated in the comment; reference the SpawnOptions
type and the DockerSpawnProvider and ProcessSpawnProvider derivation logic so
future callers get a TS error instead of relying solely on runtime throws.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 252ad118-1653-41b1-b49c-45a719fd8a31

📥 Commits

Reviewing files that changed from the base of the PR and between 849e13c and 9acb5e5.

📒 Files selected for processing (16)
  • .gitignore
  • README.md
  • bridge/bridge.mjs
  • bridge/spawn-from-env.mjs
  • package.json
  • skill/SKILL.md
  • src/cli.ts
  • src/config.ts
  • src/gateway.ts
  • src/inject.ts
  • src/setup.ts
  • src/spawn/docker.ts
  • src/spawn/process.ts
  • src/spawn/types.ts
  • src/types.ts
  • tsconfig.json

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 16 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="bridge/spawn-from-env.mjs">

<violation number="1" location="bridge/spawn-from-env.mjs:32">
P2: `agentExited` listener is registered too late, creating a race that can hang the script if the agent exits quickly.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread bridge/spawn-from-env.mjs Outdated
Comment on lines +32 to +49
const agent = await client.spawnPty({
name,
cli,
args,
channels,
task: process.env.AGENT_TASK,
cwd: process.env.AGENT_CWD,
});

await new Promise((resolve) => {
client.addListener('agentExited', (event) => {
if (event.name === agent.name) {
resolve();
}
});
});
} finally {
await client.shutdown().catch(() => {});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: agentExited listener is registered too late, creating a race that can hang the script if the agent exits quickly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At bridge/spawn-from-env.mjs, line 32:

<comment>`agentExited` listener is registered too late, creating a race that can hang the script if the agent exits quickly.</comment>

<file context>
@@ -0,0 +1,56 @@
+  });
+
+  try {
+    const agent = await client.spawnPty({
+      name,
+      cli,
</file context>
Suggested change
const agent = await client.spawnPty({
name,
cli,
args,
channels,
task: process.env.AGENT_TASK,
cwd: process.env.AGENT_CWD,
});
await new Promise((resolve) => {
client.addListener('agentExited', (event) => {
if (event.name === agent.name) {
resolve();
}
});
});
} finally {
await client.shutdown().catch(() => {});
const waitForExit = new Promise((resolve) => {
const onAgentExited = (event) => {
if (event.name === name) {
client.removeListener('agentExited', onAgentExited);
resolve();
}
};
client.addListener('agentExited', onAgentExited);
});
await client.spawnPty({
name,
cli,
args,
channels,
task: process.env.AGENT_TASK,
cwd: process.env.AGENT_CWD,
});
await waitForExit;

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 6 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="bridge/spawn-from-env.mjs">

<violation number="1" location="bridge/spawn-from-env.mjs:6">
P2: Workspace key precedence is inverted: when both env vars are set, this bridge still uses `RELAY_API_KEY` instead of preferring `RELAY_WORKSPACE_KEY`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread bridge/spawn-from-env.mjs
Comment on lines +6 to +8
if (!process.env.RELAY_API_KEY && process.env.RELAY_WORKSPACE_KEY) {
process.env.RELAY_API_KEY = process.env.RELAY_WORKSPACE_KEY;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Workspace key precedence is inverted: when both env vars are set, this bridge still uses RELAY_API_KEY instead of preferring RELAY_WORKSPACE_KEY.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At bridge/spawn-from-env.mjs, line 6:

<comment>Workspace key precedence is inverted: when both env vars are set, this bridge still uses `RELAY_API_KEY` instead of preferring `RELAY_WORKSPACE_KEY`.</comment>

<file context>
@@ -1,53 +1,13 @@
-
-  if (!name) {
-    throw new Error('AGENT_NAME is required');
+  if (!process.env.RELAY_API_KEY && process.env.RELAY_WORKSPACE_KEY) {
+    process.env.RELAY_API_KEY = process.env.RELAY_WORKSPACE_KEY;
   }
</file context>
Suggested change
if (!process.env.RELAY_API_KEY && process.env.RELAY_WORKSPACE_KEY) {
process.env.RELAY_API_KEY = process.env.RELAY_WORKSPACE_KEY;
}
if (process.env.RELAY_WORKSPACE_KEY) {
process.env.RELAY_API_KEY = process.env.RELAY_WORKSPACE_KEY;
}

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