Skip to content

[2/3] Add guarded structured execution and resumable interactions - #2992

Draft
George Ng (GeorgeNgMsft) wants to merge 3 commits into
georgengmsft-structured-action-contractsfrom
georgengmsft-guarded-action-execution
Draft

[2/3] Add guarded structured execution and resumable interactions#2992
George Ng (GeorgeNgMsft) wants to merge 3 commits into
georgengmsft-structured-action-contractsfrom
georgengmsft-guarded-action-execution

Conversation

@GeorgeNgMsft

@GeorgeNgMsft George Ng (GeorgeNgMsft) commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

This PR lets clients run a specific TypeAgent action directly, while keeping the existing execution queue and safety checks. It asks for confirmation unless an action is explicitly marked read-only, lets users answer questions or forms and resume the same action without running it again, and returns the actual results or a clear failure, cancellation, or uncertain outcome. It also lets the originating client reconnect to unfinished work without giving another client control, and fixes a test-runner issue uncovered by the new coverage.

Stack boundary

Layer 2 of the replacement for #2973, based on #2991 (georgengmsft-structured-action-contracts, 9316cb9adfe5147cfc20ef689cfd48ea08297be5). This PR intentionally targets the layer-1 branch, not main. Layer 3 owns MCP/Direct adapters and integration documentation. Nothing here merges the stack or closes the source PR.

Changes

  • Add transport-neutral Dispatcher.executeAction, continueAction, and cancelAction methods and existing dispatcher RPC plumbing. Execute typed actions directly through the existing FIFO/request lifecycle and executeActions, without @action serialization, translation, replay, or implicit prior-turn bindings.
  • Guard exact action identity, version, caller scope, live contract fingerprint, parameters, enablement, readiness, and policy before preparation and handler entry. Do not initiate setup. Require action-bound confirmation unless policy explicitly declares read-only, honoring explicit confirmation requirements even for read-only actions.
  • Resume blocking questions/forms/proposals and SDK yes/no, multi-choice, pick-and-remember, and form callbacks in the same suspended operation. Validate typed responses and single-use interaction IDs before consuming them; preserve queue cancellation, abort, telemetry, and serialization. Cancellation never selects an affirmative default.
  • Return explicit completed/failed/cancelled/interaction/stale/unavailable/uncertain outcomes, readable output, and actual ActionResult values, entities, and IDs. Preserve nested command errors and fallback failures without reasoning retries. Guard internal children and flow steps, preserving parent flow text/data without replaying consumed children.
  • Add opt-in conversation joins with server-issued in-memory reconnect capabilities. Reconnect takes over the same logical scope; stale connections, other owners/conversations, expired capabilities, and lost server state cannot resume it. Preserve existing disconnect grace and supersession. Exclude capabilities from transport diagnostics.
  • Add cancellation cleanup to SDK ChoiceManager, agent RPC, and its built-in-agent forwarding points without altering ordinary NL choice defaults.
  • Stabilize process.emit in the existing Jest setup before signal-exit can add an enumerable own property during module linking. Preserve instrumentation and normal callback cleanup, with deterministic real-signal-exit regression coverage.

Host/API handoff

Join a concrete TypeAgent conversation using structuredActions: {} and retain JoinConversationResult.structuredActions.resumeToken only in trusted memory. Resume by joining the same conversationId with structuredActions: { resumeToken }; latest authorized connection wins. Existing non-opt-in clients cannot execute structured actions. This is narrow logical ownership within the existing local-server trust model, not a new authenticated multi-user boundary.

Execution requires protocol version 1, scopeId, schemaName, actionName, contract fingerprint, and concrete parameters. Continue with the returned operationId, opaque interactionId, and typed response; cancellation bypasses the suspended FIFO command. An authorized terminal repeat returns the retained cloned result, never another execution. Lost initial delivery or lost retained state must not be replayed automatically.

Bounded state: 100 live operations per context, 10-minute operation/interaction lifetime, latest 100 terminal outcomes retained for 10 minutes, and 100 host logical bindings with a 30-minute idle lifetime. Raw PowerShell flow steps fail closed as unavailable until they have discoverable per-step contracts; typed flow actions and their children are supported.

Validation

  • Dispatcher and affected dependency-aware builds; pinned Prettier and diff checks.
  • Default dispatcher pnpm --filter agent-dispatcher run test:local passed twice: 130 suites and 2,087 tests passed, with one preexisting skipped suite/test. No new skips or test-order changes.
  • Smallest combined execution/discovery reproduction passes: 81 tests in the same Jest invocation. Script-level Node tests pass: 50 tests, including two deterministic signal-exit cases for inherited and existing own emit.
  • 462 focused offline tests across dispatcher (210), dispatcher RPC (19), agent RPC (40), SDK (37), shared server/host (52), and reconnect/client lifecycle (104).
  • Real dispatcher/fake SDK and real shared-server/RPC tests cover zero-effect guards, immutable confirmation, wrong/duplicate responses, forms and callbacks, reconnect ownership, no-client grace, uncertainty, queue coexistence, true results, and legacy compatibility.
  • Final review regressions cover failing system.config.toggleAgent without false completion and parent flow text/data versus child results with no replay. Focused re-review found no residual significant issue.
  • Official committed-head lint, complexity, circular dependency, and debt ratchets pass against the layer-1 base; no new violations or cycles, and new source files remain within 25 cyclomatic/30 cognitive limits.

The initial combined/default Jest failure is resolved in eff2a883c63fb96f58909f64657eafefa4bc83eb. Root cause: signal-exit@3, through proper-lockfile, assigned inherited process.emit, changing enumerable export names between Jest SyntheticModule construction and evaluation. The existing setup handled __signal_exit_emitter__ but not emit. The new inherited-property regression fails without the correction; combined and default package runs now pass without splitting discovery into a separate process.

Run typed actions through the existing dispatcher queue with live contract, scope and confirmation guards. Preserve true results, validate and resume interactions without replay, and bind trusted host reconnects to the same logical owner.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Materialize inherited process.emit before signal-exit registration can add an enumerable export during Jest ESM linking. Cover inherited and instrumented own emit properties, wrapper cleanup and real signal-exit callback behavior without host-process side effects.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant