Skip to content

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

Description

@colbylwilliams

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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions