Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/good-adults-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Checkpoint, Expert, RunSetting } from "@perstack/core"
import { describe, expect, it, vi } from "vitest"
import { SingleRunExecutor } from "./single-run-executor.js"
import { CoordinatorExecutor } from "./coordinator-executor.js"

// Mock dependencies
vi.mock("../helpers/index.js", () => ({
Expand Down Expand Up @@ -105,15 +105,15 @@ const createMockCheckpoint = (overrides?: Partial<Checkpoint>): Checkpoint =>
...overrides,
}) as Checkpoint

describe("@perstack/runtime: single-run-executor", () => {
describe("SingleRunExecutor", () => {
describe("@perstack/runtime: coordinator-executor", () => {
describe("CoordinatorExecutor", () => {
it("can be instantiated with no options", () => {
const executor = new SingleRunExecutor()
const executor = new CoordinatorExecutor()
expect(executor).toBeDefined()
})

it("can be instantiated with options", () => {
const executor = new SingleRunExecutor({
const executor = new CoordinatorExecutor({
shouldContinueRun: async () => true,
storeCheckpoint: async () => {},
eventListener: () => {},
Expand All @@ -123,7 +123,7 @@ describe("@perstack/runtime: single-run-executor", () => {
})

it("executes and returns run result with checkpoint", async () => {
const executor = new SingleRunExecutor()
const executor = new CoordinatorExecutor()
const setting = createMockSetting()

const result = await executor.execute(setting)
Expand All @@ -138,7 +138,7 @@ describe("@perstack/runtime: single-run-executor", () => {

it("creates initial checkpoint when no checkpoint provided", async () => {
const { createInitialCheckpoint } = await import("../helpers/index.js")
const executor = new SingleRunExecutor()
const executor = new CoordinatorExecutor()
const setting = createMockSetting()

await executor.execute(setting)
Expand All @@ -148,7 +148,7 @@ describe("@perstack/runtime: single-run-executor", () => {

it("creates next step checkpoint when checkpoint provided", async () => {
const { createNextStepCheckpoint } = await import("../helpers/index.js")
const executor = new SingleRunExecutor()
const executor = new CoordinatorExecutor()
const setting = createMockSetting()
const checkpoint = createMockCheckpoint()

Expand All @@ -159,7 +159,7 @@ describe("@perstack/runtime: single-run-executor", () => {

it("emits init event when eventListener is provided", async () => {
const eventListener = vi.fn()
const executor = new SingleRunExecutor({ eventListener })
const executor = new CoordinatorExecutor({ eventListener })
const setting = createMockSetting()

await executor.execute(setting)
Expand All @@ -170,7 +170,7 @@ describe("@perstack/runtime: single-run-executor", () => {
})

it("does not emit init event when eventListener is not provided", async () => {
const executor = new SingleRunExecutor()
const executor = new CoordinatorExecutor()
const setting = createMockSetting()

// Should not throw
Expand All @@ -179,7 +179,7 @@ describe("@perstack/runtime: single-run-executor", () => {

it("passes isDelegatedRun flag to getSkillManagers", async () => {
const { getSkillManagers } = await import("../skill-manager/index.js")
const executor = new SingleRunExecutor()
const executor = new CoordinatorExecutor()
const setting = createMockSetting()
const checkpoint = createMockCheckpoint({
delegatedBy: {
Expand All @@ -205,7 +205,7 @@ describe("@perstack/runtime: single-run-executor", () => {
it("passes resolveExpertToRun to setupExperts", async () => {
const { setupExperts } = await import("../helpers/index.js")
const resolveExpertToRun = vi.fn().mockResolvedValue({} as Expert)
const executor = new SingleRunExecutor({ resolveExpertToRun })
const executor = new CoordinatorExecutor({ resolveExpertToRun })
const setting = createMockSetting()

await executor.execute(setting)
Expand All @@ -216,7 +216,7 @@ describe("@perstack/runtime: single-run-executor", () => {
it("passes shouldContinueRun to executeStateMachine", async () => {
const { executeStateMachine } = await import("../state-machine/index.js")
const shouldContinueRun = vi.fn().mockResolvedValue(true)
const executor = new SingleRunExecutor({ shouldContinueRun })
const executor = new CoordinatorExecutor({ shouldContinueRun })
const setting = createMockSetting()

await executor.execute(setting)
Expand All @@ -231,7 +231,7 @@ describe("@perstack/runtime: single-run-executor", () => {
it("passes storeCheckpoint to executeStateMachine", async () => {
const { executeStateMachine } = await import("../state-machine/index.js")
const storeCheckpoint = vi.fn()
const executor = new SingleRunExecutor({ storeCheckpoint })
const executor = new CoordinatorExecutor({ storeCheckpoint })
const setting = createMockSetting()

await executor.execute(setting)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { LLMExecutor } from "../llm/index.js"
import { getSkillManagers, getSkillManagersFromLockfile } from "../skill-manager/index.js"
import { executeStateMachine } from "../state-machine/index.js"

export type SingleRunExecutorOptions = {
export type CoordinatorExecutorOptions = {
shouldContinueRun?: (setting: RunSetting, checkpoint: Checkpoint, step: Step) => Promise<boolean>
storeCheckpoint?: (checkpoint: Checkpoint) => Promise<void>
storeEvent?: (event: RunEvent) => Promise<void>
Expand All @@ -35,23 +35,23 @@ export type SingleRunExecutorOptions = {
lockfile?: Lockfile
}

export type SingleRunResult = {
export type CoordinatorResult = {
checkpoint: Checkpoint
expertToRun: Expert
experts: Record<string, Expert>
}

/**
* Executes a single run (state machine execution) without any loop or delegation handling.
* Executes a single coordinator run (state machine execution) without any loop or delegation handling.
* This is the core orchestration unit that should NOT call run() recursively.
*
* Note: This executes a complete state machine run until it reaches a terminal state
* (completed, stoppedByInteractiveTool, stoppedByDelegate, etc.), not just a single step.
*/
export class SingleRunExecutor {
constructor(private options: SingleRunExecutorOptions = {}) {}
export class CoordinatorExecutor {
constructor(private options: CoordinatorExecutorOptions = {}) {}

async execute(setting: RunSetting, checkpoint?: Checkpoint): Promise<SingleRunResult> {
async execute(setting: RunSetting, checkpoint?: Checkpoint): Promise<CoordinatorResult> {
const adapter = await createProviderAdapter(setting.providerConfig, {
proxyUrl: setting.proxyUrl,
})
Expand Down
10 changes: 5 additions & 5 deletions packages/runtime/src/orchestration/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
export {
CoordinatorExecutor,
type CoordinatorExecutorOptions,
type CoordinatorResult,
} from "./coordinator-executor.js"
export {
buildReturnFromDelegation,
type DelegationContext,
Expand All @@ -7,8 +12,3 @@ export {
type DelegationRunOptions,
extractDelegationContext,
} from "./delegation-executor.js"
export {
SingleRunExecutor,
type SingleRunExecutorOptions,
type SingleRunResult,
} from "./single-run-executor.js"
4 changes: 2 additions & 2 deletions packages/runtime/src/run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { run } from "./run.js"
const mockExecute = vi.fn()
const mockBuildReturnFromDelegation = vi.fn()

// Mock SingleRunExecutor as a class
// Mock CoordinatorExecutor as a class
vi.mock("./orchestration/index.js", async (importOriginal) => {
const original = await importOriginal<typeof import("./orchestration/index.js")>()
return {
...original,
SingleRunExecutor: class MockSingleRunExecutor {
CoordinatorExecutor: class MockCoordinatorExecutor {
execute = mockExecute
},
buildReturnFromDelegation: (...args: unknown[]) => mockBuildReturnFromDelegation(...args),
Expand Down
6 changes: 3 additions & 3 deletions packages/runtime/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import {
} from "./helpers/index.js"
import {
buildReturnFromDelegation,
CoordinatorExecutor,
DelegationExecutor,
extractDelegationContext,
SingleRunExecutor,
} from "./orchestration/index.js"

export type RunOptions = {
Expand Down Expand Up @@ -61,7 +61,7 @@ const defaultCreateJob = (
* - Delegation routing (single vs parallel)
* - Terminal state detection
*
* Each run execution is delegated to SingleRunExecutor.
* Each run execution is delegated to CoordinatorExecutor.
*/
export async function run(runInput: RunParamsInput, options?: RunOptions): Promise<Checkpoint> {
const runParams = runParamsSchema.parse(runInput)
Expand All @@ -83,7 +83,7 @@ export async function run(runInput: RunParamsInput, options?: RunOptions): Promi
}
storeJob(job)

const runExecutor = new SingleRunExecutor({
const runExecutor = new CoordinatorExecutor({
shouldContinueRun: options?.shouldContinueRun,
storeCheckpoint: options?.storeCheckpoint,
storeEvent: options?.storeEvent,
Expand Down