scope-check
Use before authoring any change to the ai-conductor harness repository, and before creating any new skill, to decide three things deterministically: whether the change is harness-repo-only or consumer-facing, whether a new skill belongs in the shipped `skills/` catalog or this repository's local `.agents/skills/` catalog, and whether the change is provider-agnostic. Produces a placement verdict and the registration steps that verdict requires.
- License 许可
- Apache-2.0
- 最近更新
- 2026-11-11
- Stars
- ★ 7
- Forks
- 2
01 前言 Front Matter
- name
- scope-check
- description
- Use before authoring any change to the ai-conductor harness repository, and before creating any new skill, to decide three things deterministically: whether the change is harness-repo-only or consumer-facing, whether a new skill belongs in the shipped `skills/` catalog or this repository's local `.agents/skills/` catalog, and whether the change is provider-agnostic. Produces a placement verdict and the registration steps that verdict requires.
- disable-model-invocation
- true
02 资源文件 1
- 参考文档agents/openai.yaml358 B
03 SKILL.md 正文
Scope Check
Three scope questions recur on every change to this repository, and each has a cheap wrong answer that is expensive to unwind. Run the decision procedures below before authoring, not after.
This repository is the harness. It authors skills, agents, an engine (src/conductor), and
behavioral rules that get installed into other repositories — "consumer repositories" — which then
use the harness to build their own software. It also builds itself with itself (self-host). Those
two audiences share a checkout, which is exactly why scope errors are easy to make here and nowhere
else.
Answer A, then B, then C. B is only reached when the change adds a skill. C applies to every change.
1. Decision A — harness-repo-only, or consumer-facing?
Question: does this change serve this repository's own construction, or every repository that installs the harness?
Procedure
Walk the signals in order. The first HALT wins; a signal that does not fire is not evidence for the other side.
Step 1 — repo-only signals. Answer YES to any of these and the change is harness-repo-only:
- It touches self-host, daemon, or sandbox machinery — anything under
src/conductor/src/engine/self-host/, the live-boundary fingerprint, the isolated provider home, or a code path gated behindisSelfBuild(). That gate is the canonical shape of a repo-only feature: it isdaemon && selfHost, so every other repository's bytes are unchanged. - It touches this repository's own validation or release gates —
test/test_harness_integrity.sh,test/lint_shell.sh,test/test_provider_skill_contracts.sh,bin/generate-model-table, the release-gate classifier,.docs/release-waivers/. - It touches this repository's own CI (
.github/workflows/) or its.ai-conductor/config.ymlcustom steps. - It depends on a convention only this repository has — its
.docs/layout, its[Unreleased]changelog gate, itsVERSIONfile, its shipped-record ledger, its own branch policy.
Step 2 — consumer signals. If no repo-only signal fired, answer this:
Would a repository that installed the harness, has no daemon, has no
.docs/history, and has never run a self-host build still benefit from this change?
- YES → consumer-facing.
- NO → harness-repo-only.
This question is a general-benefit heuristic and over-generalizes a change whose subject is a
mechanism that exists only here — ask first whether that mechanism exists outside this repository.
See the caveat under AGENT_INSTRUCTIONS.md → Scope Decisions: this skill's verdict is an input,
not the decision, and a conflict with the operator's plain request is surfaced before landing.
Step 3 — mixed changes are split, never averaged. A change that fires both a repo-only signal and the consumer question is two changes. Land the consumer-facing behavior in the shared surface and the repo-only behavior in the repo-local surface, and say so in the diff. Do not ship a shared rule with a repo-only escape clause buried in it.
Where the artifact goes
| Verdict | Rules go in | Docs go in | Skill catalog |
|---|---|---|---|
| Consumer-facing | HARNESS.md |
docs/ (reference/, guides/, explanation/, runbooks/) |
skills/ |
| Harness-repo-only | AGENT_INSTRUCTIONS.md (the file CLAUDE.md and AGENTS.md both point at) |
docs/guides/self-hosting.md, docs/contributing/ |
.agents/skills/ |
HARNESS.md is the single source of truth for behavioral rules consumed by projects using the
harness. AGENT_INSTRUCTIONS.md describes how this repository is built. A rule written into the
wrong one is either invisible to consumers or imposed on them without cause.
Follow the existing precedent
This repository already states the boundary explicitly, in a fixed sentence shape, wherever a rule is
deliberately repo-only. The release-disposition rule in AGENT_INSTRUCTIONS.md uses:
"For consumer projects without this custom-step configuration, the global harness … convention remains unchanged."
When Decision A returns harness-repo-only for a rule that narrows or replaces a shared convention, write that sentence. It is the established contract, and omitting it is what makes a reader assume a repo-local rule is universal. When the repo-only rule is purely additive — a new gate consumers have no analogue for — no such sentence is needed, because there is no shared convention being narrowed.
Cost of getting A wrong
- Repo-only shipped as consumer-facing. Every consumer repository inherits machinery it has no
daemon, no
.docs/ledger, and no self-host build to satisfy. The failure is not loud — it is a gate that can never pass in a repository that was never supposed to run it, and the operator there has no context to diagnose why. - Consumer-facing buried as repo-only. A genuinely universal capability is trapped behind this repository's private conventions. It never installs, never appears in the shipped catalog, and the next author re-implements it — badly — because the existing one looked repo-specific.
2. Decision B — shipped skills/, or local .agents/skills/?
Reached only when the change adds a skill. There are exactly two catalogs.
skills/<name>/SKILL.md |
.agents/skills/<name>/SKILL.md |
|
|---|---|---|
| Audience | every repository that installs the harness | this repository only |
| Installed by | bin/install, which auto-discovers every skills/<name>/ containing a SKILL.md |
nothing — never installed anywhere |
| Discovery homes | both user-space catalogs: ~/.claude/skills and ~/.agents/skills |
in-repo only: .agents/skills/ natively, plus a .claude/skills/<name> symlink |
| Current members | the shipped lifecycle catalog | every directory under .agents/skills/ |
Procedure
- Would a consumer repository ever invoke it? If NO →
.agents/skills/. Stop. - Does it encode conventions only this repository has — its validation suite, its release gates,
its
.docs/layout, its self-host build, its own test-isolation policy? If YES →.agents/skills/. Stop. - Is it a lifecycle capability — something that belongs to a phase of building software in any
repository (understand, decide, build, ship)? If YES →
skills/. - Still ambiguous? Default to
.agents/skills/. Promoting a local skill to the shipped catalog later is additive and reversible; demoting a shipped skill breaks every installation that already symlinked it.
The registration asymmetry — why B is load-bearing
The two catalogs cost radically different amounts to join, because this repository's integrity suite
globs skills/*/SKILL.md and nothing else.
A skills/ addition must satisfy all of:
- Frontmatter with all four required fields —
name,description,enforcement,phase(integrity check 2). - A row in
ARCHITECTURE.md's model-selection table (check 5). That region is generated: its source issrc/conductor/src/engine/model-table-metadata.tsplus the resolved config, rendered bybin/generate-model-table. Hand-editingARCHITECTURE.mdfails check 5a's drift gate — the fix is to update the metadata and regenerate. - Agreement between the model table's tier and the skill's own
model:pin, in both directions (check 5b). - Every
`/skill-name`reference it makes must resolve to a realskills/directory (check 4). - The provider contract audit in
test/test_provider_skill_contracts.sh, which is mechanically hostile to Claude-only phrasing (see Decision C). - Automatic symlinking into both user-space catalogs on the next
bin/install, for every consumer.
A .agents/skills/ addition requires none of the shipped catalog's model-table registration.
Its frontmatter is deliberately minimal — name, description, and the Claude invocation-policy
field only; no enforcement, no phase, no model — and docs/reference/skills.md states that
exclusion in so many words. Integrity check 2a still includes the local catalog so invocation policy
cannot drift between hosts.
Two consequences follow, and both matter:
- The catalog choice is a real fork, not a filing preference. One path drags six gates and a global install; the other drags none.
- The local catalog remains outside the broader provider-language audit, so provider-agnosticism is still a human obligation beyond the mechanically checked invocation policy. Decision C is therefore more important for a local skill, not less.
What a local skill still owes
.agents/skills/<name>/SKILL.md— frontmatter ofname,description, anddisable-model-invocation: true; local skills are explicit-only by default..agents/skills/<name>/agents/openai.yaml— the Codex-side interface block, matching the existing local skills:interface:withdisplay_name,short_description, anddefault_prompt, pluspolicy.allow_implicit_invocation: falsefor parity with Claude.- A symlink
.claude/skills/<name> → ../../.agents/skills/<name>, committed as a git symlink (mode120000), so the skill is discoverable from the other supported host too. This is the local catalog's provider-parity mechanism — see Decision C. - An entry in
docs/reference/skills.mdunder Repository-local skills, plus its two counts at the top of that file. - If it is wired as an engine step, a
steps:entry in.ai-conductor/config.ymlpointing at its.agents/skills/<name>/SKILL.mdpath. Operator-invoked local skills need no wiring.
Worked example — this skill
scope-check runs Decision B on itself.
- Step 1: would a consumer repository ever invoke it? No. Its entire subject is the boundary between this repository and its consumers, the two catalogs this repository maintains, and the registration gates this repository enforces. A repository that merely installs the harness has one catalog, no model-selection table to regenerate, and no consumer of its own.
- Verdict:
.agents/skills/scope-check/SKILL.md.
Placing it in skills/ would ship a skill about this repository's private authoring decisions into
every consumer's user-space catalog, where its advice is not merely useless but actively wrong —
telling an operator to weigh a HARNESS.md-versus-AGENT_INSTRUCTIONS.md split that does not exist
in their repository. That is precisely the error Decision A exists to prevent, committed by the
artifact meant to prevent it. It is recorded here as the worked example on purpose.
Cost of getting B wrong
- Local skill filed as shipped. It installs into every consumer's
~/.claude/skillsand~/.agents/skillson their next update, where a host agent can match itsdescriptionand invoke it against a repository whose layout it describes incorrectly. Removing it later is a breaking change to installed state, not a revert. - Shipped skill filed as local. It never installs anywhere. No consumer can reach it, it gets no
model-table row, no
enforcement/phasedeclaration, and no provider audit — so it silently accumulates provider-specific assumptions until promotion becomes a rewrite.
3. Decision C — is it provider-agnostic?
Every supported host must be able to do the thing. Nothing may work only on Claude or only on Codex.
Procedure
Answer each. Any YES in steps 1–3 requires the remedy in step 4 before the change lands.
-
Does it name a provider-specific path, environment variable, config file, or CLI flag? Examples that are provider-specific:
~/.claude/skills,~/.agents/skills,CLAUDE_CONFIG_DIR,CODEX_HOME,.claude/settings.local.json,.claude/skills/. Naming one is allowed; relying on one without its counterpart is not. -
Does it rely on a capability only one provider has? Interactive slash commands, a specific one host's subagent facility, a model-selection parameter, a settings key one host reads and the other ignores.
-
Is there an equivalent seam on the other side? If a seam exists, use both. If no seam exists, the capability is genuinely unsupported on that host and must fail closed with a diagnostic that names the selected provider and a recovery action —
HARNESS.md's unsupported-capability contract, whichtest/test_provider_skill_contracts.shasserts. It must not silently degrade. -
Remedy — scope, don't remove. Provider mechanics stay; they get an explicit owner on the same line. The house pattern, which the audit's own fixtures encode:
Claude Code invokes
conductas/conduct; Codex invokes it as$conduct.Claude Code uses its Agent tool with a model parameter; other hosts use the selected host's available subagent facility.
The shared outcome and the shared gate are stated once, provider-neutrally, and only the invocation mechanics fork.
What is already mechanized — point at it, do not restate it
test/test_provider_skill_contracts.shruns in the integrity suite. It does two things: it pins required provider-neutral language inHARNESS.mdand in the shipped skills, and it runs a deterministic audit that rejects the specific ways an instruction accidentally becomes Claude-only — an unscoped imperative slash command, an unscoped reference to Claude Code's Agent tool, an unscoped model parameter, unscoped Claude-subagent delegation, an unscoped interactive slash command. It also carries fixtures proving each category fails for the right reason. Read it before writing new prose about provider neutrality; it is the specification.- Root instruction parity is structural, not editorial.
CLAUDE.mdandAGENTS.mdare both symlinks toAGENT_INSTRUCTIONS.md, and integrity check 15 asserts exactly that. Claude readsCLAUDE.md, Codex readsAGENTS.md; because both resolve to one file, a change to this repository's agent instructions reaches both hosts by construction. Never break that by converting either into a regular file to hold host-specific text — that is what makes them drift, and it is the failure check 15 exists to catch. Host-specific guidance goes inside the shared file, scoped on the line. - The local catalog's parity mechanism is the symlink pair.
.agents/skills/is already Codex's native project-local location; the committed.claude/skills/<name>symlink makes the identical file reachable from Claude. One file, two discovery paths — the same construction as the root instructions. A local skill that exists in only one of the two is reachable by only one host.test/check_local_skill_host_wiring.sh(integrity check 2b) rejects a local skill without a resolving.claude/skills/<name>symlink and an orphaned.claude/skillsentry;test/check_skill_invocation_policy.sh(integrity check 2a) rejects a local skill without itsagents/openai.yamlpolicy.
Real asymmetries this decision must catch
These are live in this repository. They are the shape of the trap, not hypotheticals.
- Discovery and invocation are separate host seams.
bin/installenumeratesskills/*/containing aSKILL.mdand symlinks the identical set into~/.claude/skillsand~/.agents/skills; it has no per-provider catalog filter. Invocation control is paired instead: Claude readsdisable-model-invocation: truefromSKILL.md, while Codex readspolicy.allow_implicit_invocation: falsefromagents/openai.yaml. Adding only one side recreates the exact provider-parity failure Decision C exists to prevent. Engine-rendered/skillor$skillprompts remain explicit and continue to work. .claude/settings.local.jsonhas no Codex counterpart. That file carries apermissions.allow[]array that Claude reads and Codex does not. Anything whose behavior depends on it — an allowed command, a suppressed prompt — is Claude-only by construction, and will appear to work because the operator tested it on Claude. Behavior that both hosts must share cannot live in one host's settings file.- The isolated provider home — a correct seam, and a real provider difference inside it.
src/conductor/src/engine/self-host/provider-home.tsprovisions a throwaway home and setsCLAUDE_CONFIG_DIRfor Claude orCODEX_HOMEfor Codex from a singleHOME_VARIABLEmap. That is the shape to imitate: one seam, both sides named, and gated behindisSelfBuild()— literallydaemon && selfHostinsrc/conductor/src/engine/conductor.ts— so every other repository's build path is byte-for-byte unchanged. Note what the file had to absorb to stay symmetric: it copiesskills/rather than symlinking it, because Codex's skill discovery writes.system/bookkeeping back through a live link into the git-tracked worktree; and it creates a Codex-only<home>/.agents/skills → <home>/skillslink, because Codex discovers by that directory name while Claude reads its config dir. Claude's parallel path is a different file entirely (self-host/sandbox-build-env.ts, which symlinks). Same outcome, two mechanisms, both explicit — that is what "provider-agnostic" means in practice. It does not mean identical code. - Ambient credentials are not symmetric. The same provisioning path deletes
CLAUDE_CODE_OAUTH_TOKENfrom the child environment. It is a Claude-side variable with no Codex equivalent; assuming a token variable exists on both sides is the same class of error in reverse. ~/.codex/skillsis a legacy Codex location;~/.agents/skillsis the active one. Installation cleans up the former. A change that writes only one of the two user-space catalogs halves the corpus for one host, and duplicate discovery across the legacy and active Codex directories is an install failure, not a warning.
Cost of getting C wrong
The change works — for the operator who wrote it, on the provider they happened to be running. It fails for the other host at a time and place with no connection to the commit that caused it: a skill that never triggers, an override that never applies, a catalog that is half-populated. Provider asymmetry does not produce errors; it produces absence, which is far harder to trace.
4. Output
Emit the three verdicts explicitly before authoring, and carry them into the change description:
Scope check
A. Audience: harness-repo-only | consumer-facing — <the signal that decided it>
B. Catalog: skills/ | .agents/skills/ | n/a — <the step that decided it>
C. Provider: agnostic | scoped-with-both-seams | unsupported-and-fails-closed
Registration: <the list this verdict requires, from §1 and §2>
If A and B disagree — a consumer-facing rule paired with a local skill, or vice versa — that is not a nuance to reconcile in prose. Re-run A; one of the two answers is wrong.
Verification
- [ ] Decision A run, with the specific signal that decided it named — not "feels repo-specific"
- [ ] Rules landed in
HARNESS.md(consumer-facing) orAGENT_INSTRUCTIONS.md(repo-only), never both by copy - [ ] A repo-only rule that narrows a shared convention carries the established "For consumer projects … remains unchanged" sentence
- [ ] Mixed-scope change split into its consumer-facing and repo-only halves, not averaged into one rule
- [ ] Decision B run for every new skill, with its deciding step named
- [ ] A
skills/addition declaresname,description,enforcement,phase - [ ] A
skills/addition is registered viasrc/conductor/src/engine/model-table-metadata.tsandbin/generate-model-table—ARCHITECTURE.md's generated table is not hand-edited - [ ] A
.agents/skills/addition declaresname,description, anddisable-model-invocation: true, and adds no other fields the local catalog does not use - [ ] A
.agents/skills/addition ships itsagents/openai.yamlinterface block withpolicy.allow_implicit_invocation: falseand a committed.claude/skills/<name>symlink (git mode120000) - [ ] Decision C run; every provider-specific path, variable, or capability either has its counterpart or is scoped to its host on the same line
- [ ]
CLAUDE.mdandAGENTS.mdremain symlinks toAGENT_INSTRUCTIONS.md— no host-specific text was added by splitting them - [ ]
docs/reference/skills.mdupdated: the new skill's entry and the counts at the top of the file - [ ] Documentation upkeep satisfied per this repository's rule — the canonical affected page updated in the same change
- [ ] Changelog decision made deliberately: an entry under
[Unreleased]only for a notable reader-visible implementation change - [ ]
test/test_harness_integrity.shpasses, including check 5a (model-table drift), check 15 (root instruction parity), and the provider skill contract suite
04 同仓库更多 Skills
build-review-dependency-hygiene
Judge whether a feature diff adds unnecessary, insufficiently pinned, or license-incompatible dependencies.
writing-system-tests
Use BEFORE implementing any feature that has stories in .docs/stories/ — generates failing acceptance specs from acceptance criteria as the RED phase of TDD. Generates HTTP/request-level acceptance tests for headless/API projects, end-to-end UI tests for projects with a frontend, using the project's own test framework and directory conventions.
verify-claims
Use only when an active workflow is about to rely on a nontrivial factual claim or assumption for a spec, plan, ADR, finding, or code decision. Do not invoke for casual questions, status reports, restatements, or trivially verifiable mechanics.
tdd
Use when implementing any feature or bugfix. Five-step cycle: RED → DOMAIN → GREEN → DOMAIN → COMMIT. Enforces test-first development with domain integrity review at every phase boundary.
stories
Use only within active engineer/conduct DECIDE after architecture approval, when a committed `.docs/stories` acceptance artifact is required. Do not invoke for general user-story examples, requirements discussion, or direct implementation requests.
stack-land
Use when an operator needs to land reviewed manual changes on the correct closed child of a stacked feature. Operator-invoked only: inspect ownership, propose the safe child with evidence, and require confirmation before opening the landing.