Add Agent Factories Authoring Surface#2009
Conversation
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
e60aeab to
e290d1b
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
e512ad2 to
0a95a6f
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
e33c993 to
34fbf49
Compare
This comment has been minimized.
This comment has been minimized.
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.
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.
Address Copilot review feedback on the agent orchestrations PR (SDK side): - Document the restricted structured-output schema subset at the contract boundary on OrchestrationJsonSchema: only a conservative keyword set is honored (type/required/enum/const/properties/items/anyOf/oneOf/allOf); unsupported constraints (pattern, minLength, format, additionalProperties, numeric ranges, boolean schemas) are ignored, and oneOf is treated like anyOf. It is a best-effort accept-or-retry guard, not full JSON Schema. - Regenerate rpc.ts from the updated runtime contract, dropping the removed orchestration.runNested method and OrchestrationRunNestedRequest (nesting is forbidden; the context-level rejection is covered by orchestration.test.ts).
Renames the authoring surface from "orchestration" to "factory" per the
updated product name "agent factories":
- defineOrchestration -> defineFactory; OrchestrationContext/Definition/Handle/
Meta/Limits -> Factory*; SessionOrchestrationApi -> SessionFactoryApi
(session.factory); OrchestrationRunError -> FactoryRunError.
- joinSession({ orchestrations }) -> joinSession({ factories }).
- orchestration.ts -> factory.ts (and its test); generated/rpc.ts regenerated
from the renamed runtime contract (orchestration.* -> factory.*).
The context primitives (step, agent, parallel, pipeline, phase, log, run) keep
their names. Companion runtime rename lands in github/copilot-agent-runtime#12953.
Address Copilot review feedback on the agent factories PR (SDK side): - Reject a factory `timeout` limit above 2^31-1 ms in defineFactory's limit validation. Node clamps setTimeout delays above that to ~1ms, which would make a large declared timeout halt the run almost immediately. Adds a test. - Regenerate rpc.ts to drop the removed `parentRunId` field from FactoryExecuteRequest (factory nesting is forbidden; the field was never populated by the runtime).
Update joinSession onPermissionRequest tests to spy on resumeSessionForExtension (the method joinSession now calls to thread factories). The old spy on resumeSession no longer intercepts, so the real method ran and hung to a 30s timeout once the prettier gate stopped short-circuiting the suite.
Address CCR review feedback (clear/low-churn items): - Reject duplicate factory names in registerFactories so reverse dispatch is deterministic (silently overwriting a name left the runtime advertising two declarations for one closure). Adds a test. - Re-export FactoryRunResult from factory.ts, the package root, and the extension entry point so consumers can name the type returned by SessionFactoryApi methods and carried on FactoryRunError.envelope.
Deviation-review CR-5 (SDK side): the parallel/pipeline combinators mapped every thunk/stage error to null, including the AbortError raised when a run is cancelled mid-agent(). That let an aborted run report success with null results instead of propagating cancellation. Re-throw abort-shaped failures from both combinators (via an isFactoryAbortError guard) so cancellation bubbles out; ordinary per-item failures still map to null. Adds a test that a parallel-wrapped aborted agent() call rejects with AbortError.
Phase 3 (SDK side): ad-hoc limits and resume consent. Adds limits to the public RunOptions (typed as the public FactoryLimits) and forwards it plus resumeFromRunId in the session.factory.run request copy; refactors validateFactoryLimits to take a limits object; regenerates the wire types (FactoryRunLimits, factory_resume_declined). Paired with runtime Shift 3. Verification (Node 22): npm run build (0); npm test -- factory (31). Ships-dark: generated types track the runtime schema; SDK CI codegen is expectedly red until the runtime publishes and the @github/copilot pin bumps.
Phase 4 (SDK side): remove background execution mode. Removes background from the public RunOptions, the run() overloads, and the session.factory.run request copy; regenerates the wire types. Paired with runtime Shift 4. Verification (Node 22): npm run build (0); factory tests (31). Ships-dark CI note applies.
34fbf49 to
372f740
Compare
Cross-SDK Consistency ReviewSummary: This PR adds the Agent Factories authoring surface exclusively to the Node.js/TypeScript SDK. No equivalent feature exists in Python, Go, .NET, Java, or Rust. Finding: New experimental feature, Node.js only
|
| SDK | Equivalent surface needed |
|---|---|
| Python | define_factory(), join_session(factories=[...]), session.factory.run() |
| Go | DefineFactory(), JoinSession with Factories option, session.Factory.Run() |
| .NET | DefineFactory(), JoinSession with Factories property, session.Factory.RunAsync() |
| Java | defineFactory(), joinSession with factories list, session.factory().run() |
| Rust | define_factory(), session factory API |
No changes are required at this time. The PR is internally consistent with the Node.js SDK's established patterns for experimental features.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
awmgmcpg
To allow these domains, add them to the
network.allowedlist in your workflow frontmatter:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
Generated by SDK Consistency Review Agent for #2009 · 40.3 AIC · ⌖ 7.69 AIC · ⊞ 5K · ◷
What
Adds the SDK authoring surface for Agent Factories. Extensions can define a factory with
defineFactory({ meta, run }), register it by passing factory handles tojoinSession({ factories }), and run it from a session throughsession.factory.run(name, options), withgetRunandcancelfor run management.RunOptionsaccepts per-invocation ad-hoclimitsand aresumeFromRunIdso a caller can retry the same run under a raised, user-approved budget after a limit failure. The factory body receives a context that exposes the durable factory 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 Factories over a JSON-RPC contract, but extension authors need an ergonomic, typed TypeScript surface to define and run factories rather than hand-writing wire calls. This adds that friendly layer and the factory context primitives that let a multi-agent factory 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