Skip to content

[1/3] Add shared structured action contract discovery - #2991

Merged
George Ng (GeorgeNgMsft) merged 9 commits into
mainfrom
georgengmsft-structured-action-contracts
Sep 18, 2026
Merged

George Ng (GeorgeNgMsft) merged 9 commits into
mainfrom
georgengmsft-structured-action-contracts

Conversation

@GeorgeNgMsft

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

Copy link
Copy Markdown
Contributor

This PR adds the shared Dispatcher foundation for progressive TypeAgent action discovery. A caller supplies a required free-text query and receives up to five complete contracts ranked by TypeAgent's existing semantic action index. If embeddings are unavailable or query ranking fails, discovery falls back to deterministic substring matching and returns every literal match. This is layer 1 of the three-PR refactor of #2973; it defines discovery and contracts but intentionally leaves guarded execution and MCP/Direct exposure to the next layers.

Changes

  • Adds Dispatcher.searchActions({ query }) and carries it through Dispatcher RPC without adding a separate contract lookup.
  • Introduces an internal ActionCandidateRanker seam inside agent-dispatcher; ActionSchemaSemanticMap implements it using the existing action embeddings and semanticSearchActionSchema() remains as a compatibility wrapper.
  • Filters inactive, disabled, or host-denied schemas before ranking or disclosure, then resolves ranked identities against the current parsed schema before hydrating contracts.
  • Returns closed TypeScript contracts containing the selected action's exact schemaName/actionName identity, parameters, required and optional fields, nested definitions, enums, recursive references, policy, output metadata, and interaction metadata.
  • Keeps ranking scores internal. Semantic results are ordered by score with stable schema/action identity tie-breaking.
  • Falls back to case-insensitive contiguous substring matching over schema name, action name, and description only when semantic ranking is unavailable or fails.
  • Rebuilds dynamic-schema semantic entries off to the side and swaps them atomically, preventing partial indexes and stale contract hydration.
  • Settles schema-catalog readiness when background MCP startup fails while preserving the existing schema error for status and enablement behavior.
  • Applies the conservative confirmation policy: unclassified and state-changing actions require confirmation; only explicitly read-only actions may avoid it.
  • Keeps protocol version and trusted scope ID on the search response, while action contracts carry no fingerprint or schema hash.
  • Updates the canonical design so execution resolves the exact identity live and validates parameters against the current schema, availability, authorization, readiness, and confirmation policy.

Direct flow

Copilot -> searchActions({ query }) -> ranked complete contracts
Copilot -> executeAction(identity, scope, parameters) [layer 2]

The shared service is transport-neutral. This PR does not expose searchActions as an MCP tool yet, and Copilot cannot call it through MCP until layer 3 adds the thin adapter. Layer 2 adds guarded execution using live identity lookup and current parameter validation; there is no hidden schema hash/version equivalence check and no contract_stale result. Layer 2 also owns current availability and authorization checks, confirmation, structured results, and interaction lifecycle handling. Layer 3 adds the fixed MCP/Direct operations and conversation mapping. Ordinary user-originated natural language continues through TypeAgent's existing translation pipeline.

Validation

  • agent-dispatcher, @typeagent/dispatcher-types, @typeagent/dispatcher-rpc, and affected dependents built successfully.
  • Focused structured-action discovery and semantic-map suites passed: 30 tests.
  • Focused Dispatcher RPC suite passed: 18 tests.
  • Changed-file formatting, whitespace checks, and lint, complexity, circular-dependency, and test-debt gates passed.
  • Two independent adversarial reviews were completed. Their confirmed findings - fresh-embedding identity drift, stale dynamic-schema indexing, failed-startup readiness blocking, and environment-sensitive fixtures - were fixed and covered by regression tests.

@GeorgeNgMsft George Ng (GeorgeNgMsft) changed the title Structured action contracts [1/3] Structured action contracts Sep 11, 2026
@GeorgeNgMsft George Ng (GeorgeNgMsft) changed the title [1/3] Structured action contracts [1/3] Add shared structured action discovery and contracts Sep 11, 2026
@GeorgeNgMsft
George Ng (GeorgeNgMsft) added this pull request to stack #2994 September 11, 2026 07:20
@GeorgeNgMsft
George Ng (GeorgeNgMsft) marked this pull request as ready for review September 11, 2026 18:30
@GeorgeNgMsft George Ng (GeorgeNgMsft) changed the title [1/3] Add shared structured action discovery and contracts [1/3] Add shared structured action contract discovery Sep 16, 2026
Expose exact action discovery and closed TypeScript contracts through Dispatcher and RPC. Fingerprint execution-relevant schema and policy, report cached availability, and support trusted logical scope reuse without enabling execution.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Only expose active, enabled actions through discovery and exact contract lookup, and remove detailed availability state from the public protocol.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Return every matching closed contract from a required natural-language query, removing pagination, internal catalog filters, and the separate contract lookup.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Record BM25-style relevance ranking as the planned replacement for substring matching while preserving the two-call discovery and execution flow.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@datduyng Dominic Nguyen (datduyng) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider removing fingerprint - it's fine if there are version mismatches, keep it simple is my suggestion

Image

@GeorgeNgMsft
George Ng (GeorgeNgMsft) force-pushed the georgengmsft-structured-action-contracts branch from 9ca20b7 to 7307a05 Compare September 18, 2026 00:39
@GeorgeNgMsft
George Ng (GeorgeNgMsft) added this pull request to the merge queue Sep 18, 2026
@GeorgeNgMsft
George Ng (GeorgeNgMsft) removed this pull request from the merge queue due to a manual request Sep 18, 2026
Use live action identity and current schema validation as the execution compatibility boundary instead of contract hashes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@GeorgeNgMsft
George Ng (GeorgeNgMsft) added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit be534e0 Sep 18, 2026
27 checks passed
Rio Yu (rioyu123) pushed a commit to rioyu123/TypeAgent that referenced this pull request Sep 19, 2026
This PR adds a safe way for clients to run a specific TypeAgent action
using structured input. It uses the dispatcher’s existing queue and
action engine, asks the user before making changes, pauses when the
action needs more information, and returns the action’s real result. It
also lets the same conversation reconnect to unfinished work without
allowing another client to take control.

## Where this fits

This is layer 2 of the structured-action stack:

1. microsoft#2991 lets clients search for actions and inspect their input
contracts.
2. **This PR safely runs the selected action and manages its
lifecycle.**
3. microsoft#2993 exposes the feature through MCP and Direct Action adapters.

microsoft#2991 has merged, so this draft now targets `main`. Native stack #2994
still tracks `[2991, 2992, 2993]`.

## What changed

- Add `executeAction`, `continueAction`, and `cancelAction` to the
public dispatcher API and dispatcher RPC.
- Run typed actions through the existing dispatcher queue and
`executeActions` engine. The implementation does not turn parameters
back into command text or send them through natural-language
interpretation.
- Look up the requested action by its exact `schemaName` and
`actionName`. Search ranking is only used to help clients discover
actions; it never decides which action is executed.
- Recheck the current scope, permissions, enabled state, readiness,
input schema, parameters, and safety policy before the action starts and
again after waits.
- Ask for confirmation unless the action is explicitly marked read-only.
A read-only action can still require confirmation, and a caller cannot
bypass confirmation by claiming that it already has approval.
- Pause and resume the same operation when an action asks a question,
shows a form, proposes an edit, or requests a choice. Continuing an
operation does not run the action again.
- Cancel structured operations without choosing a default answer. This
public `cancelAction` is separate from the existing internal agent-RPC
cancellation message: it checks the conversation scope and returns a
structured result.
- Return clear outcomes: `completed`, `failed`, `cancelled`,
`requires_interaction`, `unavailable`, or `execution_uncertain`.
Completed operations include the real action output, values, and
entities.
- Guard child actions and flow steps separately while allowing them to
use results created earlier in the same operation. The operation does
not silently reuse context from an earlier conversation turn.
- Add an opt-in reconnect token for structured actions. The token
belongs to one conversation and stays in memory. A reconnect takes
ownership from the old connection, while stale or unrelated clients are
rejected.
- Clean up cancelled SDK choices across in-process and agent-RPC
execution without changing normal chat behavior.
- Fix a Jest ESM issue where `signal-exit` could change the exported
shape of `process` while tests were loading.

## API flow

First, the client joins a conversation and opts into structured actions:

```ts
const joined = await joinConversation(io, {
    conversationId,
    structuredActions: {},
});

const resumeToken = joined.structuredActions.resumeToken;
```

The client searches for an action through the API added in microsoft#2991,
chooses one exact contract, and sends concrete parameters:

```ts
const search = await dispatcher.searchActions({ query: "save this item" });

const result = await dispatcher.executeAction({
    protocolVersion: 1,
    scopeId: search.scopeId,
    schemaName: "items",
    actionName: "save",
    parameters: { value: "example" },
});
```

If the action needs confirmation or another answer, it returns
`requires_interaction`. The client sends the answer back to the same
operation:

```ts
await dispatcher.continueAction({
    protocolVersion: 1,
    scopeId: result.scopeId,
    operationId: result.operationId,
    interactionId: result.interactionId,
    response: { type: "confirmation", approved: true },
});
```

Cancellation uses the same `scopeId` and `operationId`. If the client
disconnects, it can rejoin the same conversation with the resume token
instead of starting the action again.

There is no contract fingerprint or `contract_stale` result. The
dispatcher checks the current action definition and validates the
current parameters each time it reaches an execution boundary. If the
action was removed or disabled, it is unavailable. If its current input
schema no longer accepts the parameters, execution fails before entering
the handler.

## State limits

To keep memory use bounded, each dispatcher context keeps at most 100
live operations. Operations and interactions expire after 10 minutes,
and the latest 100 finished results are retained for 10 minutes. The
server keeps at most 100 structured-action conversation bindings, which
expire after 30 minutes without use.

## Validation

- Built dispatcher types, dispatcher RPC, dispatcher, agent SDK, agent
RPC, and the agent-server protocol, client, and server.
- Passed the full dispatcher test run: 132 suites and 2,113 tests, with
one pre-existing skip.
- Passed 87 structured execution and discovery tests together.
- Passed 63 focused RPC, SDK cancellation, reconnect, and server/host
tests.
- Passed the complexity, lint, test-debt, and circular-dependency checks
with no new violations, skipped tests, or dependency cycles.

---------

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.

3 participants