Complete the child-customization model (per-child enable/disable + invocation matrix)#288
Complete the child-customization model (per-child enable/disable + invocation matrix)#288colbylwilliams wants to merge 4 commits into
Conversation
Add per-child enable/disable plus a symmetric agent/skill invocation matrix, completing the customization model. - Introduce `ChildCustomizationBase` with an optional `enabled` flag, shared by the five leaf child customizations (Agent/Skill/Prompt/Rule/ Hook); absent means enabled. McpServer and the container types keep their own required `enabled`. (Putting `enabled?` directly on `CustomizationBase` makes the client generators inherit the optional declaration and flip McpServer/containers to optional, disagreeing with the schema — a dedicated child base avoids that.) - Extend `session/customizationToggled` and every client reducer to toggle a top-level container OR an individual child by id. A child's effective state is `container.enabled && (child.enabled ?? true)`. - Add `disableUserInvocation` to `SkillCustomization`, and `disableModelInvocation` + `disableUserInvocation` to `AgentCustomization`. Regenerate the schema and all five client mirrors, update the customizations/actions guides and reference docs, refresh all six CHANGELOGs, and add reducer conformance fixtures (225/226). Verified across TypeScript, Rust, Go, Kotlin, and Swift. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR completes the session customization model by adding per-child enable/disable state and by making the skill/agent invocation gating symmetric between user-driven and model-driven invocation. It updates the canonical TypeScript protocol types/reducer, propagates the change through schema + generated client mirrors, and updates docs, changelogs, and conformance fixtures.
Changes:
- Add an optional
enabled?: booleanto leaf child customization types and extendsession/customizationToggledto target either a container or a child byid. - Add
disableUserInvocationto skills, and add bothdisableModelInvocation+disableUserInvocationto agents to complete the user/model invocation matrix. - Update conformance reducer fixtures, docs, schemas, and all client implementations (reducers + generated types) accordingly.
Show a summary per file
| File | Description |
|---|---|
| types/test-cases/reducers/225-session-customizationtoggled-toggles-child-by-id.json | Adds fixture coverage for toggling a child customization by id. |
| types/test-cases/reducers/226-session-customizationtoggled-is-no-op-for-unknown-child-id.json | Adds fixture coverage for unknown child id no-op behavior. |
| types/channels-session/state.ts | Introduces ChildCustomizationBase and adds enabled?: boolean + invocation gating fields to relevant child types. |
| types/channels-session/reducer.ts | Extends session/customizationToggled reducer logic to match top-level first, then fall back to child search. |
| types/channels-session/actions.ts | Updates action docs to describe container-or-child targeting and effective enabled precedence. |
| schema/state.schema.json | Regenerates schema to include child enabled and new invocation gating fields. |
| schema/notifications.schema.json | Regenerates schema mirrors for notification-side type refs. |
| schema/errors.schema.json | Regenerates schema mirrors for error-side type refs. |
| schema/commands.schema.json | Regenerates schema mirrors for command-side type refs. |
| schema/actions.schema.json | Regenerates action schema + updates SessionCustomizationToggledAction docs. |
| docs/guide/customizations.md | Documents per-child enable/disable, precedence, and the symmetric invocation matrix. |
| docs/guide/actions.md | Updates action reference text for session/customizationToggled. |
| CHANGELOG.md | Adds spec-level Unreleased entries for child enable/disable + invocation gating changes. |
| clients/typescript/CHANGELOG.md | Adds TypeScript client Unreleased entries reflecting the new behavior/surface. |
| clients/swift/CHANGELOG.md | Adds Swift client Unreleased entries reflecting the new behavior/surface. |
| clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/NativeReducer.swift | Extends Swift reducer toggle implementation to support child toggles. |
| clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift | Regenerated Swift types to include child enabled + invocation gating fields. |
| clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift | Regenerated Swift actions to reflect updated toggle docs. |
| clients/rust/CHANGELOG.md | Adds Rust client Unreleased entries reflecting the new behavior/surface. |
| clients/rust/crates/ahp/src/reducers.rs | Extends Rust reducer toggle implementation to support child toggles. |
| clients/rust/crates/ahp-types/src/state.rs | Regenerated Rust types to include child enabled + invocation gating fields. |
| clients/rust/crates/ahp-types/src/actions.rs | Regenerated Rust actions docs for container-or-child toggle targeting. |
| clients/kotlin/CHANGELOG.md | Adds Kotlin client Unreleased entries reflecting the new behavior/surface. |
| clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt | Extends Kotlin reducer toggle implementation to support child toggles. |
| clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt | Regenerated Kotlin types to include child enabled + invocation gating fields. |
| clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt | Regenerated Kotlin actions docs for container-or-child toggle targeting. |
| clients/go/CHANGELOG.md | Adds Go client Unreleased entries reflecting the new behavior/surface. |
| clients/go/ahptypes/state.generated.go | Regenerated Go types to include child Enabled + invocation gating fields. |
| clients/go/ahptypes/actions.generated.go | Regenerated Go actions docs for container-or-child toggle targeting. |
| clients/go/ahp/reducers.go | Extends Go reducer toggle implementation to support child toggles. |
Review details
- Files reviewed: 26/30 changed files
- Comments generated: 2
- Review effort level: Low
The SessionCustomizationToggled child-search fallback in the canonical TypeScript reducer and the hand-written Kotlin reducer built a full copy of the customizations list via map() even when no child matched, then discarded it. Replace the map()+changed-flag with an early-return loop that scans in place and only copies the list (and the matched container's children) when a child id actually matches, returning the original state untouched on the no-op path. No behavioral change: same outputs for every fixture (060/061/062/225/ 226), fewer allocations on the no-match path. The Rust, Go, and Swift reducers already used early-return loops and are unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| * Targets either a top-level container (plugin or directory) or an | ||
| * individual child (a skill, agent, or other entry inside a container) by | ||
| * `id`, and sets that entry's `enabled` flag. Disabling a container still | ||
| * disables all of its children — the effective state of a child is | ||
| * `container.enabled && (child.enabled ?? true)` — so toggling a child | ||
| * only matters while its container is enabled. Is a no-op when no | ||
| * customization (container or child) has the given `id`. |
There was a problem hiding this comment.
Fixed in 211ad0c. Reworded the JSDoc to say it matches id against every top-level customization first — a plugin or directory container, or a bare top-level MCP server — then against container children, and dropped the "(container or child)" qualifier on the no-op clause. Regenerated the Go/Rust type mirrors and JSON schema from the updated JSDoc.
The SessionCustomizationToggled doc said the action targets a "top-level container (plugin or directory)", but the reducer matches `id` against every top-level customization first — including a bare top-level MCP server — before searching container children. Reword the action JSDoc and the customizations guide (intro, code comment, prose, and action table) so they no longer imply top-level MCP servers can't be toggled by id. Regenerated the Go/Rust type mirrors and JSON schema from the JSDoc. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
I'm OK with this in general but I didn't implement it initially for because the Copilot SDK has no way to actually do this. The SDK just takes |
…tion-model # Conflicts: # CHANGELOG.md # clients/go/CHANGELOG.md # clients/go/ahptypes/state.generated.go # clients/kotlin/CHANGELOG.md # clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt # clients/rust/CHANGELOG.md # clients/rust/crates/ahp-types/src/state.rs # clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift # clients/swift/CHANGELOG.md # clients/typescript/CHANGELOG.md # docs/guide/customizations.md # schema/actions.schema.json # schema/commands.schema.json # schema/errors.schema.json # schema/notifications.schema.json # schema/state.schema.json # types/channels-session/state.ts
Closes #285.
What & why
The customization model could express container-level enable/disable but not per-child state, and its agent/skill invocation gating was asymmetric. This completes both halves.
A. Per-child enable/disable + child-addressable toggling
ChildCustomizationBase(extendsCustomizationBase) adds an optionalenabledflag, shared by the five leaf children —AgentCustomization,SkillCustomization,PromptCustomization,RuleCustomization,HookCustomization. Absent ⇒ enabled.session/customizationToggled(and every client reducer) now matchesidagainst top-level containers first, then the children inside every container, and sets that entry'senabled.container.enabled && (child.enabled ?? true), so disabling a container still disables all its children.B. Symmetric user/model invocation matrix
SkillCustomizationgainsdisableUserInvocation(it already haddisableModelInvocation).AgentCustomizationgains bothdisableModelInvocationanddisableUserInvocation.All fields are optional and additive — backward compatible, so no protocol-version bump.
Key design decision
The issue suggested putting
enabled?on the shared base "next to id/uri/name". I initially did exactly that (onCustomizationBase) and validated the result — but the client generators (Rust/Go/Kotlin/Swift) inherit the base's optional declaration and ignore therequiredredeclaration onMcpServerCustomization/containers, flipping theirenabledto optional. The JSON-schema generator, by contrast, resolves the override correctly and keeps them required — so the clients and schema would disagree on the wire contract for containers and MCP servers.A dedicated
ChildCustomizationBasethat only the five leaf children extend (McpServer and the containers keep their own requiredenabled) sidesteps the conflict entirely: every generator agrees, and this matches the issue's "shared child base" intent.Changed surface
types/channels-session/{state,actions,reducer}.ts.225(toggle a child by id; also exercises the top-level-McpServer and skip-McpServer-container branches) and226(no-op for unknown child id). Reducer branch coverage stays at 100%.set*ChildEnabledhelper and a child-search fallback in the toggle path.docs/guide/customizations.md(Children, metadata table, Toggling) anddocs/guide/actions.md; reference docs regenerated.## [Unreleased].Verification
npm run generate(idempotent) andnpm run test(typecheck, lint, verify:release-metadata, verify:changelog, 268 reducer tests @ 100% branch coverage) pass. The shared fixtures225/226were confirmed running and green in all five implementations: TypeScript, Rust (cargo test), Go (go test ./...), Kotlin (./gradlew test), and Swift (swift test).