You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Configuring the fleet on a new or changed machine means running each plugin's setup skill, and each one re-derives the same host facts: which binaries are on PATH, core count, which identity domains exist and where their tree boundaries fall, which gh config belongs to which tree, where worktrees and reports already live. None of that discovery is plugin-specific and none of it is stored, so every machine and every re-run repeats it, and nothing records whether a kept default was examined or just never looked at.
Main has 57 plugin-level setup skills (plugins/*/skills/setup/SKILL.md). All 57 set disable-model-invocation: true and all declare user-invocable. No skill, agent, or orchestrator can invoke them: docs/conventions/invocation-mode/README.md:69 states the invocation-reach invariant (a disable-model-invocation: true skill cannot be invoked by any other skill). So an orchestrator that "drives the setup skills" is impossible in its naive form.
57 setup skills, 57 with disable-model-invocation: true, 0 missing user-invocable (grep over plugins/*/skills/setup/SKILL.md). The item's counts (47 skills, one missing user-invocable in github) are stale; the github gap no longer exists.
The gate's rationale is now recorded, which the item said it was not: docs/plugin-philosophy.md:500-503 (setup matches upstream's rule for the flag, "workflows with side effects that you want to trigger manually") and docs/conventions/invocation-mode/README.md:59 (setup skills are exception class (ii)).
plugins/claude-ops/skills/ has setup, plugins, inventory, audit-install-state; none stores host facts for reuse. No machine-profile exists anywhere in plugins/ or docs/.
Carried from the item (measured by its author on one machine, not re-measured): a full pass over 140 userConfig options across 33 plugins left 108 at default, 3 already set, 3 blocked, and needed about five genuine operator answers. Nearly all the effort was host discovery. The concrete failure it records: an option holding a profile name was recorded "keep the default" with a written rationale while a profile of that name existed on disk in the repo, and a claim that the fleet had "no per-repo config layer" was made without reading docs/conventions/config-cascade/README.md, which defines exactly that layer.
Not verified: whether 140/33 still holds on main.
Proposed approach
A driver, not a replacement. Each plugin's setup keeps its own prerequisite logic (it ships and versions with the plugin). The new surface discovers the host once, stores it, and hands each setup the answers.
Decide the unblock first; pick one:
Orchestrate by instruction. The profile emits an ordered list of /<plugin>:setup check invocations with the answers each needs; the operator types them. Respects the gate; costly for the operator.
Split read from write (recommended, and the same as hooks: missing external tools are not surfaced to the user; add a model-invocable fleet-wide prerequisites check (no auto-install) #4240's second ask): check becomes model-invocable, through either a separate model-invocable read-only skill or a documented script under each plugin's scripts/, while apply stays operator-gated. Requires a new invocation-mode class or an amendment to class (ii) in docs/conventions/invocation-mode/README.md, since that doc says the class list is the unit of extension.
Relax the gate on check only. Same effect as 2 with less structure; needs the class (ii) rationale revisited.
Design rules the item established, which the implementation must enforce structurally:
An unset option whose value is a path, profile, identity, root, or name is a question: discovery must look on disk before a verdict, and the verdict cites what it found or states that it looked and found nothing.
Verdicts are default-verified (looked, here is what was seen) or default-unexamined, never a bare keep. Do not write a value equal to its default just to mark it decided.
Resolve each value through the layer and channel its owning surface declares (docs/conventions/config-cascade/README.md, docs/conventions/hook-config-delivery/README.md) and record which supplied it.
Detect identity domains per tree (conditional git includes, per-tree GH_CONFIG_DIR): pluginConfigs has one slot, and a profile that misses the split writes one domain's identity machine-wide.
Detect local guards that will block an apply (for example immutable git-config include files) and emit the command for the operator instead.
Degrade without hard dependencies: use discovery / planning skills when installed, native agents otherwise, and say which mode ran.
Read-only until an explicit confirm gate.
Where an option cannot express what discovery found, offer to file a gap with the topology attached.
Results produced by reproducing a setup skill's probes (rather than running it) are labeled reproduced.
Placement to decide: a skill in claude-ops (owns fleet state and has a setup) versus a new plugin. Main risk is overlap with machine-health (host facts, a declared-configuration drift category), claude-config, and claude-memory; the profile should feed machine-health's drift check rather than compete with it. Name suggestion: machine-profile, with actions profile, apply, diff, explain.
Open questions: where the profile lives (plugin data dir, ~/.claude, or a repo); one document or per-domain documents with a shared machine section; what happens when the operator hand-changed a value the profile set (warn, never silently re-assert).
A recorded placement decision (claude-ops skill or new plugin) that names how it avoids duplicating machine-health.
A design doc covering storage location, per-domain shape, verdict vocabulary (default-verified / default-unexamined), and the manual-change policy.
Re-running discovery on an unchanged machine reports no change and asks nothing.
Every recorded option state carries the command or path that produced it; a verdict with no observation cannot be emitted (tested).
Discovery is read-only; apply is behind an explicit confirm.
Constraints and gotchas
Do not absorb the setup skills' logic; they own their prerequisites.
Any change to the setup contract touches 57 skills and the validate-plugin-contracts.mjs setup-contract check; each touched plugin needs a version bump and CHANGELOG entry.
The source item describes a private multi-identity machine; keep host specifics out of the design doc and tests (use fixture trees under a scratch HOME).
Context
Source: local handoff item 20260817-215914-machine-profile-setup-orchestrator-skill-proposal.md (retired into this issue). Related: #4240 (prerequisites check, same gate), docs/conventions/config-cascade/, docs/conventions/hook-config-delivery/, docs/conventions/invocation-mode/.
Problem
Configuring the fleet on a new or changed machine means running each plugin's
setupskill, and each one re-derives the same host facts: which binaries are on PATH, core count, which identity domains exist and where their tree boundaries fall, whichghconfig belongs to which tree, where worktrees and reports already live. None of that discovery is plugin-specific and none of it is stored, so every machine and every re-run repeats it, and nothing records whether a kept default was examined or just never looked at.Main has 57 plugin-level setup skills (
plugins/*/skills/setup/SKILL.md). All 57 setdisable-model-invocation: trueand all declareuser-invocable. No skill, agent, or orchestrator can invoke them:docs/conventions/invocation-mode/README.md:69states the invocation-reach invariant (adisable-model-invocation: trueskill cannot be invoked by any other skill). So an orchestrator that "drives the setup skills" is impossible in its naive form.Evidence
Verified this pass (origin/main at 946caf1):
disable-model-invocation: true, 0 missinguser-invocable(grep overplugins/*/skills/setup/SKILL.md). The item's counts (47 skills, one missinguser-invocableingithub) are stale; thegithubgap no longer exists.docs/plugin-philosophy.md:500-503(setup matches upstream's rule for the flag, "workflows with side effects that you want to trigger manually") anddocs/conventions/invocation-mode/README.md:59(setup skills are exception class (ii)).plugins/claude-ops/skills/hassetup,plugins,inventory,audit-install-state; none stores host facts for reuse. Nomachine-profileexists anywhere inplugins/ordocs/.disable-model-invocationforcheckonly and keep it forapply, and its third is a model-invocable fleet-wide prerequisites check underclaude-ops:plugins.Carried from the item (measured by its author on one machine, not re-measured): a full pass over 140
userConfigoptions across 33 plugins left 108 at default, 3 already set, 3 blocked, and needed about five genuine operator answers. Nearly all the effort was host discovery. The concrete failure it records: an option holding a profile name was recorded "keep the default" with a written rationale while a profile of that name existed on disk in the repo, and a claim that the fleet had "no per-repo config layer" was made without readingdocs/conventions/config-cascade/README.md, which defines exactly that layer.Not verified: whether 140/33 still holds on main.
Proposed approach
A driver, not a replacement. Each plugin's setup keeps its own prerequisite logic (it ships and versions with the plugin). The new surface discovers the host once, stores it, and hands each setup the answers.
Decide the unblock first; pick one:
/<plugin>:setup checkinvocations with the answers each needs; the operator types them. Respects the gate; costly for the operator.checkbecomes model-invocable, through either a separate model-invocable read-only skill or a documented script under each plugin'sscripts/, whileapplystays operator-gated. Requires a new invocation-mode class or an amendment to class (ii) indocs/conventions/invocation-mode/README.md, since that doc says the class list is the unit of extension.checkonly. Same effect as 2 with less structure; needs the class (ii) rationale revisited.Design rules the item established, which the implementation must enforce structurally:
default-verified(looked, here is what was seen) ordefault-unexamined, never a barekeep. Do not write a value equal to its default just to mark it decided.docs/conventions/config-cascade/README.md,docs/conventions/hook-config-delivery/README.md) and record which supplied it.GH_CONFIG_DIR):pluginConfigshas one slot, and a profile that misses the split writes one domain's identity machine-wide.discovery/planningskills when installed, native agents otherwise, and say which mode ran.Placement to decide: a skill in
claude-ops(owns fleet state and has asetup) versus a new plugin. Main risk is overlap withmachine-health(host facts, a declared-configuration drift category),claude-config, andclaude-memory; the profile should feedmachine-health's drift check rather than compete with it. Name suggestion:machine-profile, with actionsprofile,apply,diff,explain.Open questions: where the profile lives (plugin data dir,
~/.claude, or a repo); one document or per-domain documents with a shared machine section; what happens when the operator hand-changed a value the profile set (warn, never silently re-assert).Acceptance criteria
machine-health.default-verified/default-unexamined), and the manual-change policy.Constraints and gotchas
validate-plugin-contracts.mjssetup-contract check; each touched plugin needs a version bump and CHANGELOG entry.Context
Source: local handoff item
20260817-215914-machine-profile-setup-orchestrator-skill-proposal.md(retired into this issue). Related: #4240 (prerequisites check, same gate),docs/conventions/config-cascade/,docs/conventions/hook-config-delivery/,docs/conventions/invocation-mode/.