Add Agent Orchestrations Authoring Surface#2009
Conversation
Regenerate SDK wire types (nodejs/src/generated/rpc.ts) from the updated runtime schema for the new workflow.* methods and DTOs, and mirror WorkflowMeta/WorkflowLimits in the SDK public types (nodejs/src/types.ts) with exports via index.ts/extension.ts. No behavior wired (contract skeleton only). Verification: cd nodejs && npm run build (Node 22) exit 0. Deviations: none.
Add the SDK-side Dynamic Workflows authoring surface: defineWorkflow (opaque frozen WorkflowHandle, module-local name->{meta,run} map, optional limits that reject non-positive values), workflows?: WorkflowHandle[] on the extension JoinSessionConfig only (serializing just WorkflowMeta on the wire, never the run closure), the workflow.execute reverse handler (dispatch-by-name with a stub ctx of args+log-noop+return, structured error on unknown name) and workflow.abort handler (per-run AbortController keyed by runId), both registered before session.resume, and the session.workflow.run friendly wrapper (name/handle overloads; foreground unwraps completed result and throws exported WorkflowRunError on non-completed; background returns the envelope). Exports from index.ts and extension.ts.
SDK-only; runtime untouched; no generated files edited.
Verification (Node 22, exit 0): npm run build; npm test -- test/workflow.test.ts (12 passed); typecheck; targeted ESLint. Deviations: none.
Build the real run() WorkflowContext in the SDK workflow.execute handler (replacing the Phase 2 stub): populate args; set ctx.session to the identical CopilotSession instance joinSession returned (strict === identity, no wrapper); expose ctx.signal as the per-run AbortController's AbortSignal; and implement log()/phase() as synchronous calls that buffer each line with a monotonic seq and flush incrementally over workflow.log (at await boundaries, a short unref'd timer, and a guaranteed finally-flush so no buffered narration is lost on a throwing/aborted body). SDK side of Phase 4; runtime workflow.log handler is the companion commit. Verification (Node 22, exit 0): npm run build; npm test -- test/workflow.test.ts (16 passed); typecheck; lint. Deviations: none.
Implement ctx.agent in the SDK run() context: it calls workflow.agent carrying the closed-over workflowRunId and the model, returns the subagent result text, and trims opts to label/schema/model only (effort/isolation/agentType/tool-narrowing are not v1). Schema handling arrives in Phase 6. Verification (Node 22, exit 0): npm run build; npm test -- test/workflow.test.ts (17 passed, asserts effort is dropped). Deviations: none.
Document the supported schema subset on the WorkflowContext agent() schema option (examon validator subset, not arbitrary JSON Schema) so authors do not expect unsupported keywords. Companion to the runtime Rust parse/validate. Verification (Node 22, exit 0): npm run build; npm run typecheck; npm test -- test/workflow.test.ts. Deviations: none.
Implement ctx.parallel(thunks) as a barrier fan-out (awaits all; throwing thunk -> null) and ctx.pipeline(items, ...stages) as per-item staged flow with no barrier (throwing stage nulls that item and skips its remaining stages). Both are pure SDK control flow holding no slot; concurrency is enforced by the runtime per-run limiter (each leaf ctx.agent is a workflow.agent RPC that queues until admitted). Enforce the non-configurable 4096 per-fan-out item cap, and validate element types up front so passing already-invoked promises surfaces a clear pass-functions-not-promises diagnostic instead of silently resolving to all-nulls. Verification (Node 22, exit 0): npm run build; npm test -- test/workflow.test.ts (22 passed); typecheck. Deviations: none.
SDK-side durable step(): ctx.step(key, producer) does get-then-put across the RPC gap (workflow.journal.get -> run producer on miss -> workflow.journal.put), with a cached JSON null served as a hit and producer failures left un-journaled (retried on resume). Adds getRun() forwarding and resume integration wiring. Pairs with the runtime SQL journal/run store. Verified (workflows-scoped, exit 0): npm run build; test/workflow.test.ts (24 passed, incl. step-once + cached-null + failures-not-cached + getRun forwarding). Deviations: none.
SDK-side cancellation support for the Phase-9 limits/timeout/cancel machinery: workflow.abort trips the per-run AbortController so in-flight ctx awaits reject with an AbortError and a well-behaved body unwinds cooperatively; session.workflow.cancel forwards the runId. Pairs with the runtime enforcement, approval/feedback gate, and halt->continue flow. Verified (workflows-scoped, exit 0): npm run build; test/workflow.test.ts (both new Phase-9 tests pass: cancel forwards runId; workflow.abort rejects the in-flight await with AbortError). The lone unrelated failure is the documented win32-arm64 platform-package env wrinkle. Deviations: none.
Strengthens the SDK workflow-context unit coverage to lock the full-session trust-tier guarantee: asserts ctx.session is identical to the joinSession result (context.session === joinSessionResult and context.session.rpc === joinSessionResult.rpc, proving no facade/pruning) and that session RPC is reachable from the context (invokes context.session.rpc.tasks.list()). Test-only; pairs with the runtime E2E. Verified (workflows-scoped, exit 0): npm run build; test/workflow.test.ts 26/26. Deviations: none.
…ame identity
Makes name-based resolution the primary path in session.workflow.run (the workflow-handle overload becomes local sugar), matching how tools/commands/subagents resolve by name, and removes a now-dead module-global workflowDefinitions map (workflow.execute resolves via the session-scoped registry instead). Adds unit coverage that name resolution selects the right registered workflow and that run-by-name forwards {name, args}. Duplicate-name rejection continues to rely on the Phase-3 registration policy.
Verified (workflows-scoped, exit 0): npm run build; test/workflow.test.ts (26 passed). Deviations: none.
Nested workflows are not supported: ctx.workflow(name, args) now throws a clear "nested workflows are not supported" error synchronously at the call site and forwards NO workflow.runNested request (no RPC leaves the SDK). The parentRunId/rootRunId contract scaffolding is untouched. Pairs with the runtime workflow.runNested structured rejection. (Phase reshaped from "Nested workflows" to "Forbid workflow nesting" per a human-authorized plan amendment.) Verified (workflows-scoped, exit 0): npm run build; test/workflow.test.ts 27/27 (incl. ctx.workflow-throws-without-forwarding test asserting sendRequest is not called). Deviations: none.
Add @experimental JSDoc tags to the hand-authored Dynamic Workflows surface, matching the Canvas prior art. Covers the exported symbols in workflow.ts (defineWorkflow, WorkflowContext, WorkflowHandle, SessionWorkflowApi, WorkflowRunError, and the related option and type aliases), the WorkflowLimits and WorkflowMeta types in types.ts, the readonly workflow property on the session, and the workflows option on JoinSessionConfig. The generated wire types in generated/rpc.ts already carry @experimental because every workflow RPC method is marked stability: "experimental" in the runtime contract, so no generated files change here. This commit only annotates the hand-written ergonomic surface so consumers are warned the API may change or be removed in a future SDK or CLI release.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Adds the experimental TypeScript authoring surface for defining, registering, executing, and managing Agent Orchestrations.
Changes:
- Adds orchestration definitions, handles, context primitives, metadata, and run APIs.
- Integrates orchestration registration and reverse-RPC execution into extension sessions.
- Adds generated RPC contracts and comprehensive unit tests.
Show a summary per file
| File | Description |
|---|---|
nodejs/src/orchestration.ts |
Defines the public orchestration API. |
nodejs/src/session.ts |
Implements execution, primitives, progress, and cancellation. |
nodejs/src/client.ts |
Registers orchestrations during extension resume. |
nodejs/src/extension.ts |
Exposes orchestration registration through joinSession. |
nodejs/src/index.ts |
Exports the new public surface. |
nodejs/src/types.ts |
Adds orchestration metadata and limits. |
nodejs/src/generated/rpc.ts |
Adds orchestration RPC contracts and handlers. |
nodejs/test/orchestration.test.ts |
Tests orchestration behavior and integration. |
Review details
- Files reviewed: 7/8 changed files
- Comments generated: 7
- Review effort level: Medium
| thunks.map((thunk) => | ||
| Promise.resolve() | ||
| .then(() => thunk()) | ||
| .catch(() => null) |
| try { | ||
| previous = await stage(previous, item, index); | ||
| } catch { | ||
| return null; |
| const result = await definition.run(context); | ||
| return { result } as OrchestrationExecuteResult; |
| const stored: StoredOrchestration = { | ||
| meta: definition.meta, | ||
| run: definition.run as StoredOrchestration["run"], | ||
| }; | ||
| const handle = Object.freeze({ meta: definition.meta }) as OrchestrationHandle<TArgs, TResult>; |
| for (const handle of orchestrations) { | ||
| const definition = getOrchestrationDefinition(handle); | ||
| this.orchestrations.set(definition.meta.name, definition); | ||
| } |
| export type { | ||
| RunOptions, | ||
| SessionOrchestrationApi, | ||
| OrchestrationAgentOptions, | ||
| OrchestrationContext, | ||
| OrchestrationDefinition, | ||
| OrchestrationHandle, | ||
| OrchestrationJsonSchema, | ||
| OrchestrationPipelineStage, | ||
| OrchestrationStepOptions, | ||
| } from "./orchestration.js"; |
| const envelope = await this.rpc.orchestration.run({ | ||
| name, | ||
| args: (options?.args === undefined ? {} : options.args) as Parameters< | ||
| typeof this.rpc.orchestration.run | ||
| >[0]["args"], |
Renames the authoring surface from "workflow" to "orchestration" to match
the new product name "agent orchestrations":
- defineWorkflow -> defineOrchestration; WorkflowContext/Definition/Handle/
Meta/Limits -> Orchestration*; SessionWorkflowApi -> SessionOrchestrationApi
(session.orchestration); WorkflowRunError -> OrchestrationRunError.
- joinSession({ workflows }) -> joinSession({ orchestrations }).
- workflow.ts -> orchestration.ts (and its test); generated/rpc.ts regenerated
from the renamed runtime contract (workflow.* -> orchestration.*).
The context primitives (step, agent, parallel, pipeline, phase, log, run) keep
their names. Companion runtime rename lands in github/copilot-agent-runtime#12953.
e60aeab to
e290d1b
Compare
Cross-SDK Consistency ReviewThis PR adds the Agent Orchestrations authoring surface exclusively to the Node.js/TypeScript SDK. The feature is correctly marked Current StateThe following SDKs do not yet have an equivalent orchestration API:
AssessmentSince this is a new experimental feature, shipping it in one SDK first while the API stabilizes is reasonable. However, for long-term SDK consistency, the following public API surface will eventually need to be ported to all other SDKs:
RecommendationNo blocking issues for this PR — shipping the experimental surface in Node.js first is acceptable. I suggest tracking cross-language parity as follow-up work once the feature graduates from This review was generated by the SDK Consistency Review Agent. Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
What
Adds the SDK authoring surface for Agent Orchestrations. Extensions can define a orchestration with
defineOrchestration({ meta, run }), register it by passing orchestration handles tojoinSession({ orchestrations }), and run it from a session throughsession.orchestration.run(...), withgetRunandcancelfor run management. The orchestration body receives a context that exposes the durable orchestration primitives (agent,step,parallel,pipeline,phase,log) alongside the caller-suppliedargsand the full session. The entire hand-authored surface is annotated@experimentalso consumers know it may change.Why
The runtime exposes Agent Orchestrations over a JSON-RPC contract, but extension authors need an ergonomic, typed TypeScript surface to define and run orchestrations rather than hand-writing wire calls. This adds that friendly layer and the orchestration context primitives that let a multi-agent orchestration read as ordinary async code. Marking the surface experimental sets clear expectations while the feature stabilizes alongside the runtime.
Companion PR: github/copilot-agent-runtime#12953