From 729a7ec321ba0860b2ff1463af159cdd0e8917cf Mon Sep 17 00:00:00 2001 From: Khaliq Date: Sat, 9 May 2026 21:29:34 +0200 Subject: [PATCH 1/2] Add cloud workflow schedule CLI --- .../completed/2026-05/traj_u4ixmbqqm2y1.json | 25 ++++ .../completed/2026-05/traj_u4ixmbqqm2y1.md | 14 +++ .trajectories/index.json | 9 +- packages/cloud/src/index.ts | 4 + packages/cloud/src/types.ts | 29 +++++ packages/cloud/src/workflows.test.ts | 109 +++++++++++++++++- packages/cloud/src/workflows.ts | 108 +++++++++++++++++ src/cli/commands/cloud.test.ts | 61 ++++++++++ src/cli/commands/cloud.ts | 103 +++++++++++++++++ 9 files changed, 460 insertions(+), 2 deletions(-) create mode 100644 .trajectories/completed/2026-05/traj_u4ixmbqqm2y1.json create mode 100644 .trajectories/completed/2026-05/traj_u4ixmbqqm2y1.md diff --git a/.trajectories/completed/2026-05/traj_u4ixmbqqm2y1.json b/.trajectories/completed/2026-05/traj_u4ixmbqqm2y1.json new file mode 100644 index 000000000..e926204ea --- /dev/null +++ b/.trajectories/completed/2026-05/traj_u4ixmbqqm2y1.json @@ -0,0 +1,25 @@ +{ + "id": "traj_u4ixmbqqm2y1", + "version": 1, + "task": { + "title": "Add cloud workflow schedule CLI" + }, + "status": "completed", + "startedAt": "2026-05-09T19:26:42.106Z", + "completedAt": "2026-05-09T19:29:18.024Z", + "agents": [], + "chapters": [], + "retrospective": { + "summary": "Added agent-relay cloud schedule/schedules commands backed by the Cloud workflow schedule API.", + "approach": "Standard approach", + "confidence": 0.84 + }, + "commits": [], + "filesChanged": [], + "projectId": "/Users/khaliqgant/Projects/AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "f341463e0e66cf70566eff2dbb05e977e410a016", + "endRef": "f341463e0e66cf70566eff2dbb05e977e410a016" + } +} diff --git a/.trajectories/completed/2026-05/traj_u4ixmbqqm2y1.md b/.trajectories/completed/2026-05/traj_u4ixmbqqm2y1.md new file mode 100644 index 000000000..09898b00d --- /dev/null +++ b/.trajectories/completed/2026-05/traj_u4ixmbqqm2y1.md @@ -0,0 +1,14 @@ +# Trajectory: Add cloud workflow schedule CLI + +> **Status:** ✅ Completed +> **Confidence:** 84% +> **Started:** May 9, 2026 at 09:26 PM +> **Completed:** May 9, 2026 at 09:29 PM + +--- + +## Summary + +Added agent-relay cloud schedule/schedules commands backed by the Cloud workflow schedule API. + +**Approach:** Standard approach diff --git a/.trajectories/index.json b/.trajectories/index.json index 6fe16bd4f..50121e037 100644 --- a/.trajectories/index.json +++ b/.trajectories/index.json @@ -1,6 +1,6 @@ { "version": 1, - "lastUpdated": "2026-05-09T08:47:54.843Z", + "lastUpdated": "2026-05-09T19:29:18.146Z", "trajectories": { "traj_1775914133873_35667beb": { "title": "fix-sdk-build-resolution-workflow", @@ -324,6 +324,13 @@ "startedAt": "2026-05-09T08:37:17.563Z", "completedAt": "2026-05-09T08:47:54.686Z", "path": ".trajectories/completed/2026-05/traj_lieyyspidhfj.json" + }, + "traj_u4ixmbqqm2y1": { + "title": "Add cloud workflow schedule CLI", + "status": "completed", + "startedAt": "2026-05-09T19:26:42.106Z", + "completedAt": "2026-05-09T19:29:18.024Z", + "path": "/Users/khaliqgant/Projects/AgentWorkforce/relay/.trajectories/completed/2026-05/traj_u4ixmbqqm2y1.json" } } } diff --git a/packages/cloud/src/index.ts b/packages/cloud/src/index.ts index ba4c9121e..ff9b40437 100644 --- a/packages/cloud/src/index.ts +++ b/packages/cloud/src/index.ts @@ -16,6 +16,8 @@ export { export { runWorkflow, + scheduleWorkflow, + listWorkflowSchedules, getRunStatus, getRunLogs, cancelWorkflow, @@ -57,6 +59,8 @@ export { type AuthSessionResponse, type WorkflowFileType, type RunWorkflowResponse, + type WorkflowSchedule, + type ScheduleWorkflowOptions, type WorkflowLogsResponse, type SyncPatchResponse, SUPPORTED_PROVIDERS, diff --git a/packages/cloud/src/types.ts b/packages/cloud/src/types.ts index 4ef647b63..fd550b50e 100644 --- a/packages/cloud/src/types.ts +++ b/packages/cloud/src/types.ts @@ -95,6 +95,35 @@ export type RunWorkflowResponse = { [key: string]: unknown; }; +export type WorkflowSchedule = { + id: string; + relaycronScheduleId: string; + userId: string; + workspaceId: string; + organizationId: string; + name: string; + description: string | null; + scheduleType: 'once' | 'cron'; + cronExpression: string | null; + scheduledAt: string | null; + timezone: string; + status: string; + lastTriggeredRunId: string | null; + lastTriggeredAt: string | null; + createdAt: string; + updatedAt: string; +}; + +export type ScheduleWorkflowOptions = { + apiUrl?: string; + fileType?: WorkflowFileType; + name?: string; + description?: string; + cron?: string; + at?: string; + timezone?: string; +}; + export type WorkflowLogsResponse = { content: string; offset: number; diff --git a/packages/cloud/src/workflows.test.ts b/packages/cloud/src/workflows.test.ts index 9e7081d63..af8c17163 100644 --- a/packages/cloud/src/workflows.test.ts +++ b/packages/cloud/src/workflows.test.ts @@ -29,7 +29,14 @@ vi.mock('./auth.js', () => ({ authorizedApiFetch: (...args: unknown[]) => authorizedApiFetchMock(...args), })); -import { parseGitHubRemote, parseWorkflowPaths, relativizeWorkflowPath, runWorkflow } from './workflows.js'; +import { + listWorkflowSchedules, + parseGitHubRemote, + parseWorkflowPaths, + relativizeWorkflowPath, + runWorkflow, + scheduleWorkflow, +} from './workflows.js'; describe('relativizeWorkflowPath', () => { let tmpRoot: string; @@ -419,3 +426,103 @@ describe('runWorkflow code sync', () => { expect((runBodies[0] as { paths?: unknown }).paths).toBeUndefined(); }); }); + +describe('workflow schedules', () => { + let tmpRoot: string; + let originalCwd: string; + + beforeEach(async () => { + originalCwd = process.cwd(); + tmpRoot = await realpath(await mkdtemp(path.join(os.tmpdir(), 'cloud-schedule-workflow-'))); + process.chdir(tmpRoot); + ensureAuthenticatedMock.mockResolvedValue({ accessToken: 'token' }); + }); + + afterEach(async () => { + process.chdir(originalCwd); + await rm(tmpRoot, { recursive: true, force: true }); + vi.clearAllMocks(); + }); + + it('creates a cron schedule without one-time code sync fields', async () => { + const workflowPath = path.join(tmpRoot, 'workflow.yaml'); + await writeFile( + workflowPath, + ['version: "1.0"', 'name: eval', 'swarm:', ' pattern: dag', 'agents: []', 'workflows: []'].join('\n') + ); + const scheduleBodies: unknown[] = []; + authorizedApiFetchMock.mockImplementation(async (_auth, requestPath, init) => { + expect(requestPath).toBe('/api/v1/workflows/schedules'); + scheduleBodies.push(JSON.parse(String(init?.body))); + return { + auth: { accessToken: 'token' }, + response: new Response( + JSON.stringify({ + schedule: { + id: 'sched-1', + name: 'Hourly eval', + scheduleType: 'cron', + cronExpression: '0 * * * *', + timezone: 'UTC', + status: 'active', + }, + }), + { status: 201, headers: { 'Content-Type': 'application/json' } } + ), + }; + }); + + const result = await scheduleWorkflow(workflowPath, { + cron: '0 * * * *', + name: 'Hourly eval', + }); + + expect(result.id).toBe('sched-1'); + expect(scheduleBodies[0]).toMatchObject({ + name: 'Hourly eval', + schedule_type: 'cron', + cron_expression: '0 * * * *', + timezone: 'UTC', + workflowRequest: { + fileType: 'yaml', + }, + }); + expect( + (scheduleBodies[0] as { workflowRequest: Record }).workflowRequest.runId + ).toBeUndefined(); + expect( + (scheduleBodies[0] as { workflowRequest: Record }).workflowRequest.s3CodeKey + ).toBeUndefined(); + }); + + it('lists workflow schedules', async () => { + authorizedApiFetchMock.mockResolvedValueOnce({ + auth: { accessToken: 'token' }, + response: new Response( + JSON.stringify({ + schedules: [ + { + id: 'sched-1', + name: 'Hourly eval', + scheduleType: 'cron', + cronExpression: '0 * * * *', + timezone: 'UTC', + status: 'active', + }, + ], + }), + { status: 200, headers: { 'Content-Type': 'application/json' } } + ), + }); + + const schedules = await listWorkflowSchedules(); + + expect(authorizedApiFetchMock).toHaveBeenCalledWith( + { accessToken: 'token' }, + '/api/v1/workflows/schedules', + expect.objectContaining({ headers: { Accept: 'application/json' } }) + ); + expect(schedules).toHaveLength(1); + expect(schedules[0].id).toBe('sched-1'); + }); +}); diff --git a/packages/cloud/src/workflows.ts b/packages/cloud/src/workflows.ts index bf53967fd..d9b0128d7 100644 --- a/packages/cloud/src/workflows.ts +++ b/packages/cloud/src/workflows.ts @@ -11,7 +11,9 @@ import { defaultApiUrl, type WorkflowFileType, type RunWorkflowResponse, + type ScheduleWorkflowOptions, type WorkflowLogsResponse, + type WorkflowSchedule, type SyncPatchResponse, type PathSubmission, } from './types.js'; @@ -654,6 +656,90 @@ export async function runWorkflow( return payload as RunWorkflowResponse; } +export async function scheduleWorkflow( + workflowArg: string, + options: ScheduleWorkflowOptions = {} +): Promise { + const hasCron = typeof options.cron === 'string' && options.cron.trim().length > 0; + const hasAt = typeof options.at === 'string' && options.at.trim().length > 0; + if (hasCron === hasAt) { + throw new Error('Provide exactly one of --cron or --at.'); + } + + const apiUrl = options.apiUrl ?? defaultApiUrl(); + const auth = await ensureAuthenticated(apiUrl); + const input = await resolveWorkflowInput(workflowArg, options.fileType); + + if (input.fileType === 'ts') { + await validateTypeScriptWorkflow(input.workflow); + } else if (input.fileType === 'yaml') { + console.error('Validating workflow...'); + validateYamlWorkflow(input.workflow); + } + + const requestBody: Record = { + name: options.name?.trim() || path.basename(workflowArg), + schedule_type: hasCron ? 'cron' : 'once', + timezone: options.timezone?.trim() || 'UTC', + workflowRequest: { + workflow: input.workflow, + fileType: input.fileType, + ...(input.sourceFileType ? { sourceFileType: input.sourceFileType } : {}), + }, + }; + if (options.description?.trim()) { + requestBody.description = options.description.trim(); + } + if (hasCron) { + requestBody.cron_expression = options.cron?.trim(); + } else { + requestBody.scheduled_at = new Date(String(options.at)).toISOString(); + } + + const { response } = await authorizedApiFetch(auth, '/api/v1/workflows/schedules', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify(requestBody), + }); + + const payload = await readJsonResponse(response); + if (!response.ok) { + throw new Error(`Workflow schedule failed: ${describeResponseError(response, payload)}`); + } + + if (!isWorkflowScheduleEnvelope(payload)) { + throw new Error('Workflow schedule response was not valid JSON.'); + } + + return payload.schedule; +} + +export async function listWorkflowSchedules(options: { apiUrl?: string } = {}): Promise { + const apiUrl = options.apiUrl ?? defaultApiUrl(); + const auth = await ensureAuthenticated(apiUrl); + const { response } = await authorizedApiFetch(auth, '/api/v1/workflows/schedules', { + headers: { Accept: 'application/json' }, + }); + + const payload = await readJsonResponse(response); + if (!response.ok) { + throw new Error(`Schedule list failed: ${describeResponseError(response, payload)}`); + } + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + throw new Error('Schedule list response was not valid JSON.'); + } + + const schedules = (payload as { schedules?: unknown }).schedules; + if (!Array.isArray(schedules)) { + throw new Error('Schedule list response was not valid JSON.'); + } + + return schedules.filter(isWorkflowSchedule); +} + export async function getRunStatus( runId: string, options: { apiUrl?: string } = {} @@ -829,6 +915,28 @@ function describeResponseError(response: Response, payload: unknown): string { return `${response.status} ${response.statusText}`; } +function isWorkflowScheduleEnvelope(payload: unknown): payload is { schedule: WorkflowSchedule } { + return ( + Boolean(payload) && + typeof payload === 'object' && + !Array.isArray(payload) && + isWorkflowSchedule((payload as { schedule?: unknown }).schedule) + ); +} + +function isWorkflowSchedule(value: unknown): value is WorkflowSchedule { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return false; + } + const record = value as Record; + return ( + typeof record.id === 'string' && + typeof record.name === 'string' && + (record.scheduleType === 'once' || record.scheduleType === 'cron') && + typeof record.status === 'string' + ); +} + function isMissingFileError(error: unknown): error is NodeJS.ErrnoException { return Boolean(error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT'); } diff --git a/src/cli/commands/cloud.test.ts b/src/cli/commands/cloud.test.ts index aa1119643..b615d06bc 100644 --- a/src/cli/commands/cloud.test.ts +++ b/src/cli/commands/cloud.test.ts @@ -3,6 +3,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; const cloudMocks = vi.hoisted(() => ({ runWorkflow: vi.fn(), + scheduleWorkflow: vi.fn(), + listWorkflowSchedules: vi.fn(), getRunStatus: vi.fn(), syncWorkflowPatch: vi.fn(), })); @@ -20,8 +22,10 @@ vi.mock('@agent-relay/cloud', () => ({ 'anthropic (alias: claude), openai (alias: codex), google (alias: gemini), cursor, opencode, droid', getRunLogs: vi.fn(), getRunStatus: (...args: unknown[]) => cloudMocks.getRunStatus(...args), + listWorkflowSchedules: (...args: unknown[]) => cloudMocks.listWorkflowSchedules(...args), readStoredAuth: vi.fn(), runWorkflow: (...args: unknown[]) => cloudMocks.runWorkflow(...args), + scheduleWorkflow: (...args: unknown[]) => cloudMocks.scheduleWorkflow(...args), syncWorkflowPatch: (...args: unknown[]) => cloudMocks.syncWorkflowPatch(...args), })); @@ -65,6 +69,8 @@ describe('registerCloudCommands', () => { 'whoami', 'connect', 'run', + 'schedule', + 'schedules', 'status', 'logs', 'sync', @@ -109,6 +115,61 @@ describe('registerCloudCommands', () => { expect(optionNames).toContain('--json'); }); + it('schedule creates repeatable workflow schedules', async () => { + const { program, deps } = createHarness(); + cloudMocks.scheduleWorkflow.mockResolvedValueOnce({ + id: 'sched-1', + name: 'Hourly eval', + scheduleType: 'cron', + cronExpression: '0 * * * *', + timezone: 'UTC', + status: 'active', + lastTriggeredRunId: null, + }); + + await program.parseAsync([ + 'node', + 'agent-relay', + 'cloud', + 'schedule', + 'workflow.yaml', + '--cron', + '0 * * * *', + '--name', + 'Hourly eval', + ]); + + expect(cloudMocks.scheduleWorkflow).toHaveBeenCalledWith( + 'workflow.yaml', + expect.objectContaining({ + cron: '0 * * * *', + name: 'Hourly eval', + }) + ); + expect(deps.log).toHaveBeenCalledWith('Schedule created: sched-1'); + }); + + it('schedules lists repeatable workflow schedules', async () => { + const { program, deps } = createHarness(); + cloudMocks.listWorkflowSchedules.mockResolvedValueOnce([ + { + id: 'sched-1', + name: 'Hourly eval', + scheduleType: 'cron', + cronExpression: '0 * * * *', + timezone: 'UTC', + status: 'active', + lastTriggeredRunId: 'run-1', + }, + ]); + + await program.parseAsync(['node', 'agent-relay', 'cloud', 'schedules']); + + expect(cloudMocks.listWorkflowSchedules).toHaveBeenCalledWith(expect.objectContaining({})); + expect(deps.log).toHaveBeenCalledWith(expect.stringContaining('sched-1')); + expect(deps.log).toHaveBeenCalledWith(expect.stringContaining('run-1')); + }); + it('logs has --follow and --poll-interval options', () => { const { program } = createHarness(); const cloud = program.commands.find((command) => command.name() === 'cloud'); diff --git a/src/cli/commands/cloud.ts b/src/cli/commands/cloud.ts index 69cd7a280..1bbb7af9a 100644 --- a/src/cli/commands/cloud.ts +++ b/src/cli/commands/cloud.ts @@ -13,6 +13,8 @@ import { AUTH_FILE_PATH, REFRESH_WINDOW_MS, runWorkflow, + scheduleWorkflow, + listWorkflowSchedules, getRunStatus, getRunLogs, syncWorkflowPatch, @@ -22,6 +24,7 @@ import { normalizeProvider, type WhoAmIResponse, type WorkflowFileType, + type WorkflowSchedule, } from '@agent-relay/cloud'; import { defaultExit } from '../lib/exit.js'; @@ -128,6 +131,36 @@ function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } +function formatScheduleCadence(schedule: WorkflowSchedule): string { + if (schedule.scheduleType === 'cron') { + return `${schedule.cronExpression ?? 'cron'} (${schedule.timezone})`; + } + return schedule.scheduledAt + ? `${schedule.scheduledAt} (${schedule.timezone})` + : `once (${schedule.timezone})`; +} + +function renderSchedule(schedule: WorkflowSchedule, log: (...args: unknown[]) => void): void { + log(`Schedule created: ${schedule.id}`); + log(`Name: ${schedule.name}`); + log(`Status: ${schedule.status}`); + log(`Cadence: ${formatScheduleCadence(schedule)}`); +} + +function renderScheduleList(schedules: WorkflowSchedule[], log: (...args: unknown[]) => void): void { + if (schedules.length === 0) { + log('No workflow schedules found.'); + return; + } + + for (const schedule of schedules) { + const lastRun = schedule.lastTriggeredRunId ? ` last run ${schedule.lastTriggeredRunId}` : ' no runs yet'; + log( + `${schedule.id} ${schedule.status} ${formatScheduleCadence(schedule)} ${schedule.name} (${lastRun})` + ); + } +} + // ── Command registration ───────────────────────────────────────────────────── export function registerCloudCommands(program: Command, overrides: Partial = {}): void { @@ -375,6 +408,76 @@ export function registerCloudCommands(program: Command, overrides: Partial', 'Workflow file path or inline workflow content') + .option('--api-url ', 'Cloud API base URL') + .option('--file-type ', 'Workflow type: yaml, ts, or py', parseWorkflowFileType) + .option('--cron ', 'Cron expression, for example "0 * * * *"') + .option('--at ', 'One-time ISO timestamp, for example 2026-05-10T09:00:00Z') + .option('--timezone ', 'IANA timezone for cron schedules', 'UTC') + .option('--name ', 'Schedule name') + .option('--description ', 'Schedule description') + .option('--json', 'Print raw JSON response', false) + .action( + async ( + workflow: string, + options: { + apiUrl?: string; + fileType?: WorkflowFileType; + cron?: string; + at?: string; + timezone?: string; + name?: string; + description?: string; + json?: boolean; + } + ) => { + const started = Date.now(); + let success = false; + let errorClass: string | undefined; + try { + const result = await scheduleWorkflow(workflow, options); + if (options.json) { + deps.log(JSON.stringify(result, null, 2)); + } else { + renderSchedule(result, deps.log); + deps.log('\nList schedules: agent-relay cloud schedules'); + } + success = true; + } catch (err) { + errorClass = errorClassName(err); + throw err; + } finally { + track('cloud_workflow_schedule', { + schedule_type: options.cron ? 'cron' : 'once', + has_explicit_file_type: Boolean(options.fileType), + json_output: Boolean(options.json), + success, + duration_ms: Date.now() - started, + ...(errorClass ? { error_class: errorClass } : {}), + }); + } + } + ); + + cloudCommand + .command('schedules') + .description('List scheduled workflow runs') + .option('--api-url ', 'Cloud API base URL') + .option('--json', 'Print raw JSON response', false) + .action(async (options: { apiUrl?: string; json?: boolean }) => { + const schedules = await listWorkflowSchedules(options); + if (options.json) { + deps.log(JSON.stringify({ schedules }, null, 2)); + return; + } + renderScheduleList(schedules, deps.log); + }); + // ── status ───────────────────────────────────────────────────────────────── cloudCommand From 64c4a240c5a5deb5b13edc23237957a0a23e8413 Mon Sep 17 00:00:00 2001 From: Khaliq Date: Sat, 9 May 2026 21:44:07 +0200 Subject: [PATCH 2/2] Expose workflow schedules from SDK --- .../completed/2026-05/traj_oyc528j7suvo.json | 53 +++++++++++++++++++ .../completed/2026-05/traj_oyc528j7suvo.md | 33 ++++++++++++ .trajectories/index.json | 9 +++- packages/sdk/package.json | 1 + packages/sdk/src/workflows/cloud-schedules.ts | 3 ++ packages/sdk/src/workflows/index.ts | 1 + 6 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 .trajectories/completed/2026-05/traj_oyc528j7suvo.json create mode 100644 .trajectories/completed/2026-05/traj_oyc528j7suvo.md create mode 100644 packages/sdk/src/workflows/cloud-schedules.ts diff --git a/.trajectories/completed/2026-05/traj_oyc528j7suvo.json b/.trajectories/completed/2026-05/traj_oyc528j7suvo.json new file mode 100644 index 000000000..ed918e996 --- /dev/null +++ b/.trajectories/completed/2026-05/traj_oyc528j7suvo.json @@ -0,0 +1,53 @@ +{ + "id": "traj_oyc528j7suvo", + "version": 1, + "task": { + "title": "Expose cloud workflow scheduling through Relay SDK" + }, + "status": "completed", + "startedAt": "2026-05-09T19:43:34.805Z", + "completedAt": "2026-05-09T19:44:00.107Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-05-09T19:43:56.286Z" + } + ], + "chapters": [ + { + "id": "chap_iwcqe1x74aoc", + "title": "Work", + "agentName": "default", + "startedAt": "2026-05-09T19:43:56.286Z", + "endedAt": "2026-05-09T19:44:00.107Z", + "events": [ + { + "ts": 1778355836288, + "type": "decision", + "content": "Expose workflow schedule helpers from @agent-relay/sdk/workflows: Expose workflow schedule helpers from @agent-relay/sdk/workflows", + "raw": { + "question": "Expose workflow schedule helpers from @agent-relay/sdk/workflows", + "chosen": "Expose workflow schedule helpers from @agent-relay/sdk/workflows", + "alternatives": [], + "reasoning": "Ricky and similar products should consume scheduling from Relay SDK instead of duplicating Cloud endpoint calls. The SDK re-exports the existing @agent-relay/cloud scheduling implementation." + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Re-exported Cloud workflow schedule helpers from @agent-relay/sdk/workflows and verified SDK build plus cloud CLI schedule tests.", + "approach": "Standard approach", + "confidence": 0.9 + }, + "commits": [], + "filesChanged": [], + "projectId": "/Users/khaliqgant/Projects/AgentWorkforce/relay", + "tags": [], + "_trace": { + "startRef": "729a7ec321ba0860b2ff1463af159cdd0e8917cf", + "endRef": "729a7ec321ba0860b2ff1463af159cdd0e8917cf" + } +} diff --git a/.trajectories/completed/2026-05/traj_oyc528j7suvo.md b/.trajectories/completed/2026-05/traj_oyc528j7suvo.md new file mode 100644 index 000000000..01140f951 --- /dev/null +++ b/.trajectories/completed/2026-05/traj_oyc528j7suvo.md @@ -0,0 +1,33 @@ +# Trajectory: Expose cloud workflow scheduling through Relay SDK + +> **Status:** ✅ Completed +> **Confidence:** 90% +> **Started:** May 9, 2026 at 09:43 PM +> **Completed:** May 9, 2026 at 09:44 PM + +--- + +## Summary + +Re-exported Cloud workflow schedule helpers from @agent-relay/sdk/workflows and verified SDK build plus cloud CLI schedule tests. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Expose workflow schedule helpers from @agent-relay/sdk/workflows + +- **Chose:** Expose workflow schedule helpers from @agent-relay/sdk/workflows +- **Reasoning:** Ricky and similar products should consume scheduling from Relay SDK instead of duplicating Cloud endpoint calls. The SDK re-exports the existing @agent-relay/cloud scheduling implementation. + +--- + +## Chapters + +### 1. Work + +_Agent: default_ + +- Expose workflow schedule helpers from @agent-relay/sdk/workflows: Expose workflow schedule helpers from @agent-relay/sdk/workflows diff --git a/.trajectories/index.json b/.trajectories/index.json index 50121e037..c8740afb0 100644 --- a/.trajectories/index.json +++ b/.trajectories/index.json @@ -1,6 +1,6 @@ { "version": 1, - "lastUpdated": "2026-05-09T19:29:18.146Z", + "lastUpdated": "2026-05-09T19:44:00.209Z", "trajectories": { "traj_1775914133873_35667beb": { "title": "fix-sdk-build-resolution-workflow", @@ -331,6 +331,13 @@ "startedAt": "2026-05-09T19:26:42.106Z", "completedAt": "2026-05-09T19:29:18.024Z", "path": "/Users/khaliqgant/Projects/AgentWorkforce/relay/.trajectories/completed/2026-05/traj_u4ixmbqqm2y1.json" + }, + "traj_oyc528j7suvo": { + "title": "Expose cloud workflow scheduling through Relay SDK", + "status": "completed", + "startedAt": "2026-05-09T19:43:34.805Z", + "completedAt": "2026-05-09T19:44:00.107Z", + "path": "/Users/khaliqgant/Projects/AgentWorkforce/relay/.trajectories/completed/2026-05/traj_oyc528j7suvo.json" } } } diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 416256acc..15d4ff8ae 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -150,6 +150,7 @@ }, "dependencies": { "@agent-relay/config": "6.0.12", + "@agent-relay/cloud": "6.0.12", "@agent-relay/github-primitive": "6.0.12", "@agent-relay/slack-primitive": "6.0.12", "@agent-relay/workflow-types": "6.0.12", diff --git a/packages/sdk/src/workflows/cloud-schedules.ts b/packages/sdk/src/workflows/cloud-schedules.ts new file mode 100644 index 000000000..49b8ae0b4 --- /dev/null +++ b/packages/sdk/src/workflows/cloud-schedules.ts @@ -0,0 +1,3 @@ +export { listWorkflowSchedules, scheduleWorkflow } from '@agent-relay/cloud'; + +export type { ScheduleWorkflowOptions, WorkflowSchedule } from '@agent-relay/cloud'; diff --git a/packages/sdk/src/workflows/index.ts b/packages/sdk/src/workflows/index.ts index e507a2d67..95a8ed6dd 100644 --- a/packages/sdk/src/workflows/index.ts +++ b/packages/sdk/src/workflows/index.ts @@ -33,6 +33,7 @@ export { export * from './memory-db.js'; export * from './file-db.js'; export * from './run.js'; +export * from './cloud-schedules.js'; export * from './builder.js'; export * from './coordinator.js'; export * from './barrier.js';