Skip to content

Complete the child-customization model: per-child enable/disable + a symmetric invocation matrix #285

Description

Summary

SessionState.customizations lets a host surface host-discovered skills and custom agents as native SkillCustomization / AgentCustomization children. In practice a host hits three gaps that force it to either drop entries or lose state, because the child-customization model is only half-built:

  1. No per-child enable/disable. A skill or agent that exists but is individually turned off (config-level) has no representation. enabled lives only on container customizations, and SessionCustomizationToggled only reaches top-level containers — so an individually-disabled child can't be surfaced, only omitted.
  2. No user-invocation gate. SkillCustomization has disableModelInvocation but no user-side counterpart, so "this skill is a user slash-command vs. model-auto-invoked" can't be expressed. A host that wants to render a command palette can't tell which entries are user-invocable.
  3. Asymmetric invocation model between Skill and Agent. AgentCustomization has neither invocation gate, so "this agent is/isn't user-selectable" and "the model may/may not auto-delegate to it" are both unrepresentable.

The result is that disabled and non-user-invocable entries get dropped entirely — a settings UI can't list-and-re-enable a disabled skill, and a picker can't show (greyed out) the agents a user can't currently select.

Current state in the spec (0.4.0)

  • ChildCustomization variants (Agent, Skill, Prompt, Rule, Hook, McpServer) share id / uri / name / icons / range. None carries enabled.
  • SkillCustomization carries disableModelInvocation?: boolean.
  • AgentCustomization carries _meta but no invocation gates.
  • RuleCustomization models activation with first-class, type-specific fields (alwaysApply, globs) — i.e. the spec's established style is to model behavior with real fields, not a generic _meta slot.
  • Container customizations (PluginCustomization, DirectoryCustomization) carry enabled: boolean.
  • The SessionCustomizationToggled reducer matches a top-level container id and sets the container's enabled; it has no path to toggle an individual child.

So the spec already has (a) an enable/disable action and (b) one corner of an invocation model (Skill.disableModelInvocation). This proposal finishes both.

Proposal

A. Per-child enabled + child-addressable toggle

  • Add enabled?: boolean (absent ⇒ enabled) to the child customizations that can be individually disabled — at minimum SkillCustomization and AgentCustomization; ideally to the shared child base next to id/uri/name so it's uniform.
  • Extend SessionCustomizationToggled (and the reducer) so a child id can be toggled, not just a container. Today the reducer only matches top-level containers.
  • Define precedence explicitly: a disabled container disables its children regardless of each child's enabled (effective-enabled = container.enabled && child.enabled).

This lets a disabled-but-present customization be surfaced with accurate state instead of omitted, and generalizes cleanly — "toggle one skill/agent/rule without touching its siblings" is broadly useful.

Alternative considered: mirror the container convention with a required enabled: boolean on children. An optional field with absent⇒enabled is friendlier for existing producers (no behavior change unless they set it). Either is fine; flagging the choice.

B. A complete, symmetric invocation matrix on Skill and Agent

Add the missing gates so both child types carry the full {user, model} matrix, using the spec's existing negative-default-false convention (default = both can invoke):

Field SkillCustomization AgentCustomization
disableModelInvocation?: boolean exists add (gates model auto-delegation as a sub-agent)
disableUserInvocation?: boolean add (gates user slash-command invocation) add (gates user selection in a picker)

With (A) and (B), a host never has to drop an entry: a disabled skill is enabled: false; a non-user-invocable agent is disableUserInvocation: true; a model-only skill is disableUserInvocation: true (or a slash-only skill is disableModelInvocation: true). All are surfaced with faithful state.

Why first-class fields, not _meta

enabled and user/model invocation are general agent-host concepts, not vendor-specific. The spec already models this class of thing with real fields (Rule.alwaysApply/globs, Skill.disableModelInvocation). Routing them through _meta would defeat the purpose of having native customization types and fragment the same concept across every vendor's _meta namespace. _meta should stay reserved for genuinely provider-specific residue.

Concrete grounding (public, vendor SDK example)

A representative agent SDK already reports these per-entry on its discovery events, which is exactly the data with no AHP home:

  • session.skills_loaded skill entries report enabled and userInvocable (and a model-invocation flag is being added — see the companion SDK request).
  • session.custom_agents_updated agent entries report userInvocable, plus model and tools.

So the data exists at the source; only the AHP carrier fields are missing.

Backwards compatibility

All additions are additive and optional (booleans / optional fields), so existing producers and consumers are unaffected until they opt in. The one behavioral extension is teaching SessionCustomizationToggled to match child ids; that only changes behavior for ids that previously matched nothing.

Scope / related proposals

(A) and (B) are the core of this issue and hang together. Two adjacent improvements are tracked separately so each can land on its own:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions