From ff8c5eb70469102abc65620b9c36257be6f1ac10 Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Sat, 4 Apr 2026 11:11:45 -0700 Subject: [PATCH] fix(runtime): add queued agent run state --- .../ECOSYSTEM_IMPLEMENTATION_TRACKER.md | 11 +++++-- ...5_AGENT_RUNTIME_CONTRACT_EXECUTION_PLAN.md | 7 ++-- packages/events/src/agent-runtime.test.ts | 15 +++++++++ packages/events/src/agent-runtime.ts | 1 + .../src/modules/agent-runtime/routes.test.ts | 25 +++++++++++++++ .../src/modules/agent-runtime/routes.ts | 3 +- .../src/modules/agent-runtime/routes.test.ts | 32 +++++++++++++++++++ .../src/modules/agent-runtime/routes.ts | 3 +- 8 files changed, 90 insertions(+), 7 deletions(-) diff --git a/docs/ecosystem/ECOSYSTEM_IMPLEMENTATION_TRACKER.md b/docs/ecosystem/ECOSYSTEM_IMPLEMENTATION_TRACKER.md index 04947434..00133fd9 100644 --- a/docs/ecosystem/ECOSYSTEM_IMPLEMENTATION_TRACKER.md +++ b/docs/ecosystem/ECOSYSTEM_IMPLEMENTATION_TRACKER.md @@ -246,17 +246,24 @@ These should be resolved before claiming the ecosystem docs are fully implementa - `b8242b4` adds `GET /api/agent-runtime/actions` with canonical `AgentActionLog` projection - FlowMonk local installs now resolve `@bytelyst/*` from the sibling `learning_ai_common_plat` workspace instead of the dead localhost registry - `1ccafa7` adds FlowMonk direct runtime projections for sessions, tasks, runs, action logs, and dispatch validation +- [x] promote `queued` to a first-class `AgentRun` state and preserve it in shared runtime projections + Commits: + - `pending current commit` + Status note: + - `AgentRun` now supports `queued` directly in `@bytelyst/events` + - platform-service now preserves queued platform runs as `queued` + - cowork-service now projects pending IPC tasks as queued runs + - FlowMonk now projects scheduled entries as queued runs ### 6.1 Remaining Direct Runtime TODOs - Cowork: add `AgentTodo` direct projection once the product exposes first-class todo entities. - Cowork: attach canonical event IDs to approval and audit trails so ActionTrail lineage can stop using fallback/null semantics. - FlowMonk: add direct `AgentApprovalCheckpoint` and `AgentTodo` projections once the product exposes first-class approval/todo primitives. -- Platform-service: refine the `queued -> paused` projection fallback once run-vs-session semantics are finalized. +- Shared docs: clarify run-vs-session lifecycle guidance now that `queued` is first-class. ### 6.2 Explicit Blockers And Questions -- Question: should the shared runtime contract add a first-class `queued` run state rather than continuing the current `queued -> paused` fallback projection? - Question: should Cowork approval/audit records emit canonical event IDs from Rust so runtime projections and ActionTrail lineage can share the same identifiers? --- diff --git a/docs/ecosystem/PHASE5_AGENT_RUNTIME_CONTRACT_EXECUTION_PLAN.md b/docs/ecosystem/PHASE5_AGENT_RUNTIME_CONTRACT_EXECUTION_PLAN.md index ddd2f04c..12f23764 100644 --- a/docs/ecosystem/PHASE5_AGENT_RUNTIME_CONTRACT_EXECUTION_PLAN.md +++ b/docs/ecosystem/PHASE5_AGENT_RUNTIME_CONTRACT_EXECUTION_PLAN.md @@ -1,7 +1,7 @@ # Phase 5 Execution Plan > **Flow:** Shared agent runtime contract baseline -> **Status:** Baseline implemented, Cowork product integration implemented, FlowMonk follow-up pending +> **Status:** Baseline implemented, Cowork product integration implemented, FlowMonk product integration implemented > **Owner:** `learning_ai_common_plat` > **Purpose:** Turn the runtime contract draft into concrete schemas for sessions, tasks, todos, runs, approvals, dispatch, and action logs. @@ -98,14 +98,15 @@ Observed baseline: - `01201f8` cowork-service runtime task projection - `b8242b4` cowork-service runtime action-log projection - `1ccafa7` FlowMonk local shared-package resolution + runtime projection routes +- `a3ae6fe` FlowMonk queued-run projection preservation +- `pending current commit` shared runtime queued-run state ## 7. Remaining Gaps - Cowork now emits shared runtime projections from cowork-service, but Rust-side canonical event IDs are still missing on approval/audit records and `AgentTodo` still has no first-class product source. - FlowMonk now emits direct runtime projections for planning sessions, tasks, runs, and action logs, but it still has no first-class approval checkpoint or todo primitive. -- run-vs-session semantics for queued work still need a stricter mapping than the current projection fallback. +- run-vs-session semantics for queued work now preserve `queued` directly in the shared runtime contract, but broader session/run lifecycle guidance still needs to be documented. ## 8. Explicit Blockers And Questions -- Question: should queued work remain represented as `paused`, or should the shared runtime contract gain a first-class `queued` run state? - Question: should Cowork approval and audit records start emitting canonical event IDs from Rust so ActionTrail and runtime lineage can share the same identifiers? diff --git a/packages/events/src/agent-runtime.test.ts b/packages/events/src/agent-runtime.test.ts index fb2b9489..6050123d 100644 --- a/packages/events/src/agent-runtime.test.ts +++ b/packages/events/src/agent-runtime.test.ts @@ -117,4 +117,19 @@ describe('agent runtime contract baseline', () => { expect(todo.status).toBe('in-progress'); expect(actionLog.eventName).toBe('agent.run.started'); }); + + it('accepts queued runs as a first-class runtime state', () => { + const run = AgentRunSchema.parse({ + runId: 'run_queued_1', + sessionId: 'sess_queued_1', + productId: 'clawcowork', + status: 'queued', + startedAt: '2026-04-04T10:00:00.000Z', + completedAt: null, + checkpointArtifactId: null, + correlationId: 'corr_queued_1', + }); + + expect(run.status).toBe('queued'); + }); }); diff --git a/packages/events/src/agent-runtime.ts b/packages/events/src/agent-runtime.ts index f0b4d9a0..43813966 100644 --- a/packages/events/src/agent-runtime.ts +++ b/packages/events/src/agent-runtime.ts @@ -63,6 +63,7 @@ export const AgentTodoSchema = z.object({ }); export const AgentRunStatusSchema = z.enum([ + 'queued', 'running', 'paused', 'waiting-approval', diff --git a/services/cowork-service/src/modules/agent-runtime/routes.test.ts b/services/cowork-service/src/modules/agent-runtime/routes.test.ts index 8c42de5a..fb673453 100644 --- a/services/cowork-service/src/modules/agent-runtime/routes.test.ts +++ b/services/cowork-service/src/modules/agent-runtime/routes.test.ts @@ -100,6 +100,31 @@ describe('agent runtime routes', () => { }); }); + it('projects pending IPC tasks into queued AgentRun objects', async () => { + call.mockResolvedValue({ + result: { + tasks: [ + { + id: 'task-queued', + status: 'pending', + createdAt: '2026-04-04T08:00:00.000Z', + updatedAt: '2026-04-04T08:10:00.000Z', + sessionId: 'sess-queued', + }, + ], + }, + }); + + const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/runs' }); + expect(res.statusCode).toBe(200); + const body = JSON.parse(res.payload); + expect(body.runs[0]).toMatchObject({ + runId: 'task-queued', + sessionId: 'sess-queued', + status: 'queued', + }); + }); + it('projects IPC tasks into shared AgentTask objects', async () => { call.mockResolvedValue({ result: { diff --git a/services/cowork-service/src/modules/agent-runtime/routes.ts b/services/cowork-service/src/modules/agent-runtime/routes.ts index 2b1e32b8..7c87d2a7 100644 --- a/services/cowork-service/src/modules/agent-runtime/routes.ts +++ b/services/cowork-service/src/modules/agent-runtime/routes.ts @@ -30,6 +30,8 @@ function asIsoString(value: unknown, fallback: string): string { function mapTaskStatus(status: unknown): AgentRun['status'] { switch (status) { + case 'pending': + return 'queued'; case 'running': return 'running'; case 'completed': @@ -38,7 +40,6 @@ function mapTaskStatus(status: unknown): AgentRun['status'] { return 'failed'; case 'cancelled': return 'cancelled'; - case 'pending': default: return 'paused'; } diff --git a/services/platform-service/src/modules/agent-runtime/routes.test.ts b/services/platform-service/src/modules/agent-runtime/routes.test.ts index 4952e847..3ebea8de 100644 --- a/services/platform-service/src/modules/agent-runtime/routes.test.ts +++ b/services/platform-service/src/modules/agent-runtime/routes.test.ts @@ -101,6 +101,38 @@ describe('agentRuntimeRoutes', () => { }); }); + it('GET /agent-runtime/runs preserves queued runs as queued', async () => { + runRepoMock.listRuns.mockResolvedValue([ + { + id: 'run_queued', + productId: 'lysnrai', + kind: 'agent', + name: 'queued-run', + source: 'dispatch', + status: 'queued', + createdAt: '2026-04-04T18:00:00.000Z', + updatedAt: '2026-04-04T18:10:00.000Z', + metadata: { + sessionId: 'sess_queued', + }, + }, + ]); + const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); + + const res = await app.inject({ + method: 'GET', + url: '/api/agent-runtime/runs?limit=10', + }); + + expect(res.statusCode).toBe(200); + const body = JSON.parse(res.body); + expect(body.runs[0]).toMatchObject({ + runId: 'run_queued', + sessionId: 'sess_queued', + status: 'queued', + }); + }); + it('POST /agent-runtime/dispatch/validate validates the shared dispatch contract', async () => { const app = await buildApp({ sub: 'user_1', productId: 'lysnrai' }); diff --git a/services/platform-service/src/modules/agent-runtime/routes.ts b/services/platform-service/src/modules/agent-runtime/routes.ts index db7cbaa4..17492e04 100644 --- a/services/platform-service/src/modules/agent-runtime/routes.ts +++ b/services/platform-service/src/modules/agent-runtime/routes.ts @@ -38,6 +38,8 @@ function mapSessionStatus(session: { function mapRunStatus(status: string): AgentRun['status'] { switch (status) { + case 'queued': + return 'queued'; case 'running': return 'running'; case 'succeeded': @@ -46,7 +48,6 @@ function mapRunStatus(status: string): AgentRun['status'] { return 'failed'; case 'cancelled': return 'cancelled'; - case 'queued': default: return 'paused'; }