Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Trajectory: Add --wk shorthand for --workspace-key on SDK commands

> **Status:** ✅ Completed
> **Confidence:** 90%
> **Started:** July 15, 2026 at 06:57 PM
> **Completed:** July 15, 2026 at 06:57 PM

---

## Summary

Added --wk alias for --workspace-key via preAction hook in addSdkOptions

**Approach:** Standard approach

---

## Key Decisions

### Fold --wk into workspaceKey via a Commander preAction hook in addSdkOptions

- **Chose:** Fold --wk into workspaceKey via a Commander preAction hook in addSdkOptions
- **Reasoning:** Single choke point normalizes the alias for every reader; Commander v12 can't alias two long flags to one attribute

---

## Chapters

### 1. Work

_Agent: default_

- Fold --wk into workspaceKey via a Commander preAction hook in addSdkOptions: Fold --wk into workspaceKey via a Commander preAction hook in addSdkOptions
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"id": "traj_f8bgcpr32y71",
"version": 1,
"task": {
"title": "Add --wk shorthand for --workspace-key on SDK commands"
},
"status": "completed",
"startedAt": "2026-07-15T18:57:06.368Z",
"completedAt": "2026-07-15T18:57:13.746Z",
"agents": [
{
"name": "default",
"role": "lead",
"joinedAt": "2026-07-15T18:57:07.613Z"
}
],
"chapters": [
{
"id": "chap_pz1ubxwwsf70",
"title": "Work",
"agentName": "default",
"startedAt": "2026-07-15T18:57:07.613Z",
"endedAt": "2026-07-15T18:57:13.746Z",
"events": [
{
"ts": 1784141827614,
"type": "decision",
"content": "Fold --wk into workspaceKey via a Commander preAction hook in addSdkOptions: Fold --wk into workspaceKey via a Commander preAction hook in addSdkOptions",
"raw": {
"question": "Fold --wk into workspaceKey via a Commander preAction hook in addSdkOptions",
"chosen": "Fold --wk into workspaceKey via a Commander preAction hook in addSdkOptions",
"alternatives": [],
"reasoning": "Single choke point normalizes the alias for every reader; Commander v12 can't alias two long flags to one attribute"
},
"significance": "high"
}
]
}
],
"retrospective": {
"summary": "Added --wk alias for --workspace-key via preAction hook in addSdkOptions",
"approach": "Standard approach",
"confidence": 0.9
},
"commits": [],
"filesChanged": [],
"projectId": "AgentWorkforce/relay",
"tags": [],
"_trace": {
"startRef": "e93e5c7b8f94b35d718057baec5e4242d198dfb7",
"endRef": "e93e5c7b8f94b35d718057baec5e4242d198dfb7"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Trajectory: Persist up workspace key per-project; resolve it in CWD; add --wk to up

> **Status:** ✅ Completed
> **Confidence:** 85%
> **Started:** July 15, 2026 at 07:28 PM
> **Completed:** July 15, 2026 at 07:28 PM

---

## Summary

up persists project workspace key; CWD resolution; --wk on up

**Approach:** Standard approach

---

## Chapters

### 1. Work

_Agent: default_

- Persist workspace key per-project on up; resolve below flag/env above global store: Persist workspace key per-project on up; resolve below flag/env above global store
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"id": "traj_sgdizcyacm51",
"version": 1,
"task": {
"title": "Persist up workspace key per-project; resolve it in CWD; add --wk to up"
},
"status": "completed",
"startedAt": "2026-07-15T19:28:16.051Z",
"completedAt": "2026-07-15T19:28:23.602Z",
"agents": [
{
"name": "default",
"role": "lead",
"joinedAt": "2026-07-15T19:28:17.148Z"
}
],
"chapters": [
{
"id": "chap_a1d3oh6mby93",
"title": "Work",
"agentName": "default",
"startedAt": "2026-07-15T19:28:17.148Z",
"endedAt": "2026-07-15T19:28:23.602Z",
"events": [
{
"ts": 1784143697150,
"type": "decision",
"content": "Persist workspace key per-project on up; resolve below flag/env above global store: Persist workspace key per-project on up; resolve below flag/env above global store",
"raw": {
"question": "Persist workspace key per-project on up; resolve below flag/env above global store",
"chosen": "Persist workspace key per-project on up; resolve below flag/env above global store",
"alternatives": [],
"reasoning": ""
},
"significance": "high"
}
]
}
],
"retrospective": {
"summary": "up persists project workspace key; CWD resolution; --wk on up",
"approach": "Standard approach",
"confidence": 0.85
},
"commits": [],
"filesChanged": [],
"projectId": "AgentWorkforce/relay",
"tags": [],
"_trace": {
"startRef": "ff4b2773aabaa2c2ec3cbf2d9856b8bce77022e4",
"endRef": "ff4b2773aabaa2c2ec3cbf2d9856b8bce77022e4"
}
}
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ All notable changes to Agent Relay will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [Unreleased - Minor]

### Added

- `--wk <key>` is a shorthand for `--workspace-key` on every SDK-backed `agent-relay` command (`fleet nodes`, `workspace`, `integration`, `webhook`, …) and on `up`/`node up`; an explicit `--workspace-key` still wins when both are passed.
- `agent-relay up` records the workspace it joins (passed via `--workspace-key`/`--wk` or auto-minted) in the project data dir, and SDK-backed commands run in that directory now resolve that workspace key ahead of the machine-global active workspace, so `fleet nodes`/`node …` in a project reflect the broker's actual workspace. An explicit `--workspace-key`/`--wk` or `RELAY_WORKSPACE_KEY`/`RELAY_API_KEY` still overrides it. `fleet nodes` prints a stderr note when the key was inferred from that project record, so a stale broker workspace is visible rather than silent.

## [10.3.0] - 2026-07-15

Expand Down
49 changes: 49 additions & 0 deletions packages/cli/src/cli/commands/core.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Command } from 'commander';
import nodeFs from 'node:fs';
import os from 'node:os';
import nodePath from 'node:path';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { readProjectWorkspaceKey } from '../lib/project-workspace-key.js';

const sdkStatusClient = {
getStatus: vi.fn(async () => ({ agent_count: 0, pending_delivery_count: 0 })),
getSession: vi.fn(async () => ({ workspace_key: '' }) as { workspace_key?: string }),
Expand Down Expand Up @@ -500,6 +504,36 @@ describe('registerCoreCommands', () => {
expect(deps.log).toHaveBeenCalledWith('Broker PID: 5151');
});

it('up --state-dir records the workspace key in the default project dir, not the state dir', async () => {
// SDK commands (fleet nodes, …) read the key from the default project data
// dir and never accept --state-dir, so a redirected broker state dir must
// NOT be where the key lands — otherwise those commands miss it.
const stateDir = nodeFs.mkdtempSync(nodePath.join(os.tmpdir(), 'relay-statedir-'));
const relay = createRelayMock({ workspaceKey: 'rk_statedir_regression' });
const { program } = createHarness({ relay });

try {
const exitCode = await runCommand(program, [
'up',
'--state-dir',
stateDir,
'--workspace-key',
'rk_statedir_regression',
]);

expect(exitCode).toBeUndefined();
// Persisted at the default project data dir (the harness's mocked path)…
expect(readProjectWorkspaceKey('/tmp/project/.agentworkforce/relay')).toBe('rk_statedir_regression');
// …and NOT in the redirected state dir.
expect(readProjectWorkspaceKey(stateDir)).toBeUndefined();
} finally {
nodeFs.rmSync(stateDir, { recursive: true, force: true });
// The key is written to the real default project dir; clean it up so it
// doesn't leak into other tests running in this process.
nodeFs.rmSync('/tmp/project/.agentworkforce/relay/workspace-key.json', { force: true });
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

it('up --background re-execs a Bun standalone binary without adding its virtual entrypoint', async () => {
const spawnedProcess = createSpawnedProcessMock();
let now = 0;
Expand Down Expand Up @@ -1175,6 +1209,21 @@ describe('registerCoreCommands', () => {
expect(deps.log).toHaveBeenCalledWith('Workspace Key: rk_live_custom');
});

it('up --wk is an alias for --workspace-key', async () => {
const env: NodeJS.ProcessEnv = {};
const relay = createRelayMock({ workspaceKey: 'rk_live_alias' });
const { program, deps } = createHarness({ relay, env });

const exitCode = await runCommand(program, ['up', '--wk', 'rk_live_alias']);

expect(exitCode).toBeUndefined();
// The alias is folded into workspaceKey, so the broker sees the same env the
// explicit flag would have set.
expect(env.RELAY_WORKSPACE_KEY).toBe('rk_live_alias');
expect(env.RELAY_API_KEY).toBe('rk_live_alias');
expect(deps.log).toHaveBeenCalledWith('Workspace Key: rk_live_alias');
});

it('up without --workspace-key does not set workspace key env vars', async () => {
const env: NodeJS.ProcessEnv = {};
const relay = createRelayMock();
Expand Down
53 changes: 32 additions & 21 deletions packages/cli/src/cli/commands/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,27 +280,38 @@ export interface UpCommandOptions {
* on top of these; `local up` uses them as-is.
*/
export function addUpCommandOptions(command: Command): Command {
return command
.option('--spawn', 'Force spawn all agents from teams.json')
.option('--no-spawn', 'Do not auto-spawn agents (just start broker)')
.option('--background', 'Run broker in the background (detached)')
.option('--verbose', 'Enable verbose logging')
.option('--workspace-key <key>', 'Use a pre-established Relaycast workspace key')
.option(
'--state-dir <path>',
'Directory for broker state and connection files (default: .agentworkforce/relay/)'
)
.option('--broker-name <name>', 'Override the broker name (defaults to project directory basename)')
.option(
'--log-file <path>',
'Write structured node logs (capabilities registered, actions invoked/completed) to a file'
)
.option(
'--log-level <level>',
'Node log verbosity: debug | info | warn | error (default: info)',
parseLogLevel
)
.option('--log-json', 'Emit node logs as JSON lines instead of text');
return (
command
.option('--spawn', 'Force spawn all agents from teams.json')
.option('--no-spawn', 'Do not auto-spawn agents (just start broker)')
.option('--background', 'Run broker in the background (detached)')
.option('--verbose', 'Enable verbose logging')
.option('--workspace-key <key>', 'Use a pre-established Relaycast workspace key')
.option('--wk <key>', 'Alias for --workspace-key')
.option(
'--state-dir <path>',
'Directory for broker state and connection files (default: .agentworkforce/relay/)'
)
.option('--broker-name <name>', 'Override the broker name (defaults to project directory basename)')
.option(
'--log-file <path>',
'Write structured node logs (capabilities registered, actions invoked/completed) to a file'
)
.option(
'--log-level <level>',
'Node log verbosity: debug | info | warn | error (default: info)',
parseLogLevel
)
.option('--log-json', 'Emit node logs as JSON lines instead of text')
// Fold the `--wk` alias into `workspaceKey` before the action runs, matching
// the SDK commands' `addSdkOptions`. An explicit `--workspace-key` wins.
.hook('preAction', (thisCommand) => {
const opts = thisCommand.opts();
if (typeof opts.wk === 'string' && opts.wk.trim() && !opts.workspaceKey) {
thisCommand.setOptionValue('workspaceKey', opts.wk);
}
})
);
}

const LOG_LEVELS = ['debug', 'info', 'warn', 'error'] as const;
Expand Down
Loading
Loading