Skip to content

#1111 Declare which harnesses sync targets - #1221

Merged
williamthorsen merged 8 commits into
mainfrom
1111
Aug 6, 2026
Merged

#1111 Declare which harnesses sync targets#1221
williamthorsen merged 8 commits into
mainfrom
1111

Conversation

@williamthorsen

@williamthorsen williamthorsen commented Aug 6, 2026

Copy link
Copy Markdown
Owner

What

Guidance artifacts declared by a project (skills, subagents, rulebooks, and their dependencies) are now automatically deployed into every installed harness, and no longer depend on detection of a previous deployment when identifying which harnesses to target. Target harnesses can be declared for the user, for the project, or for a single copy of it, with command-line options available as overrides. The deployment report names the harnesses it targeted and how they were decided.

Why

Project-domain sync decided its targets by probing the repository for .claude and .rovodev. Nothing establishes that precondition: those directories hold generated output, so a repository has no reason to commit one. Claude was targeted incidentally wherever a repository committed harness config; Rovo Dev generally was not targeted at all. A repository with neither directory received no project-domain artifacts of any kind, and the run reported zero targeted harnesses rather than naming what it had skipped -- williamthorsen/operations declared an ambient rulebook and received nothing.

Which harnesses a developer runs is a fact about the user, not about a checkout, so no repository probe can answer it. The declaration puts the answer where it belongs and leaves detection as the fallback for anyone who never states it.

Details

🎉 Features

  • codeassembly.yaml accepts a harnesses: {use, drop} block in the same shape as every other declaration key. An unknown harness id fails the run, naming the file and the offending entry.
  • Sync resolves its targets in order: the --harness flag, then the declaration, then the harnesses installed under the home directory. A declaration resolving to an empty set is honored -- the run targets nothing and says so -- which is distinct from an absent declaration, which falls back.
  • harnesses resolves on a chain (the user-global tier pair, then the project tier pair), making it the one key that crosses the two declaration domains. Artifact keys continue to resolve on the project tier alone, so a user-global collections: use: [all] cannot deploy the whole catalog into every repository.
  • root: true clears only its own domain's contributions. For artifact keys this is indistinguishable from clearing the whole chain; for harnesses it is what stops a committed project file from discarding the user's global declaration. A drop still crosses the boundary, from either project-tier file.
  • Every run, dry-run included, names its targeted harnesses and what decided them. A run that fell back to detection also names the key that would pin the set, pointing at the declaration file that run actually reads.
  • sync --global honors the declaration on the same terms. install, uninstall, status, and configure-hooks deploy into the harness homes and keep detection.

🐛 Bug fixes

  • init's project scaffold named PROJECT.md as the ambient destination, which stopped receiving ambient content in Deliver project ambient rulebooks per harness #1088. It now names each harness's instance-local project guidance file (CLAUDE.local.md, AGENTS.local.md).

🧪 Tests

  • Resolver coverage for each precedence rule independently, including the two that invert easily: a project-tier root: true leaves user-global harnesses standing, while a user-global-tier one clears them.
  • A regression guard for the reported defect: harness directories the repository happens to contain no longer influence targeting.
  • Every syncCommand call in the suite injects a temp home directory, so no test reads the developer's own declaration or installed set.

📚 Documentation

  • A Harness targeting section in the agents README covers the key, the resolution order, the two-chain rule, the domain-scoped root rule, and how targeting composes with artifact-level supported-harnesses: -- targeting selects the harness set, artifact narrowing filters within it.
  • The Scopes section claimed without qualification that the declaration domains never cross. It now scopes that claim to artifact keys and names harnesses as the deliberate exception.
  • Both init scaffolds surface the harnesses key, with an example that targets a harness rather than none.

Closes #1111

`codeassembly.yaml` now takes a `harnesses` block with `use` and `drop` lists, alongside the existing artifact-type blocks. A name that does not match a known harness fails the run, naming the file and the entry that carries it.

Nothing consumes the block yet; a later change resolves it into the harnesses a sync run targets.
`sync` deploys a project's skills, subagents, and ambient rulebooks into every harness installed on the machine, whatever directories the repository itself holds. Previously it looked for `.claude` and `.rovodev` inside the repository, so a project that committed neither — most of them — received nothing for the missing harness and reported success having written no file.

A project pins its own set with the `harnesses` block in `.agents/codeassembly.yaml`, which takes `use` and `drop` lists and resolves across the user-global and project tiers. `--harness` still overrides both. `sync --global` honors the same declaration.

Because the block states which harnesses a developer runs, a project tier cannot discard what the user-global tier declared: `root: true` clears only its own tier pair. A `drop` still reaches across, so `.agents/codeassembly.local.yaml` can withdraw a harness for one checkout.

Every run now names the harnesses it targeted and what decided them, and a run that fell back to detection names the key that would pin the set.
The `codeassembly init` scaffold describes where an ambient rulebook actually lands: the ambient region of every targeted harness's machine-local project guidance file, `CLAUDE.local.md` or `AGENTS.local.md`. It had gone on naming `PROJECT.md`, which stopped receiving ambient content several releases ago.

Both the project and user-global scaffolds also carry a commented `harnesses` block, so the key that pins harness targeting is visible in the file that hosts it.
The project-declaration reference covers the `harnesses` key: its shape, the order a run resolves its targets in, and how a project's targeting composes with an artifact's own `supported-harnesses` frontmatter. It also names which commands honor the declaration and which are answered by detection alone.

Two rules readers would otherwise have to infer are now stated: `harnesses` resolves across the user-global and project tiers, alone among the keys, and `root: true` clears only the contributions of the tier pair that declares it. The scopes reference no longer claims without qualification that the domains never cross.

Passages describing where `sync` deploys say "targeted harness" rather than "detected harness", matching what decides the set.
Adds the case proving `sync --global` narrows to the harnesses its own tier declares, delivering to that harness's guidance file alone. The behavior shipped with the declaration itself; only its coverage was missing.
`sync --global` now points its pin advice at `~/.agents/codeassembly.yaml`. It had named `.agents/codeassembly.yaml`, which a global run never reads, so following the advice left the next run reporting the same fallback.

The `init` scaffolds seed `harnesses.use` with a harness id rather than an empty list. An empty list is honored as "target nothing", so a reader who uncommented the previous example verbatim disabled every deployment for that scope, having just read a comment promising the fallback instead.

The `init --global` scaffold no longer claims that only a project's gitignored `codeassembly.local.yaml` can withdraw a harness. Either project-tier file can, `drop` being unscoped by domain; only `root` is confined to its own tier pair.
The `init` scaffold cases assert the wording each template ships. Two of them guarded a deleted phrase with `not.toContain`, which encodes history rather than contract: it can fail only on a verbatim revert, so a differently-worded but equally wrong claim passes it.

The global template's withdrawal claim, previously unasserted, now has a positive case. The assertion unwraps the comment before matching, so it reads the sentence rather than the column the template happens to wrap at.
@williamthorsen williamthorsen added feature Added or improved external functionality scope:agents labels Aug 6, 2026
@williamthorsen williamthorsen self-assigned this Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Dependency audit

Production dependency audit passed.

@williamthorsen
williamthorsen marked this pull request as ready for review August 6, 2026 22:03
Harness targeting resolves entirely from the user's home directory: both the declaration it reads and the installed harnesses it falls back to live there. The README, the scaffolds `init` writes, and the code comments now describe that scope as the user's rather than the machine's, matching the `user-global` tier name used elsewhere. On a shared machine, each user carries a separate declaration and a separate set of installed harnesses.
@williamthorsen
williamthorsen merged commit a1bc4fa into main Aug 6, 2026
@williamthorsen
williamthorsen deleted the 1111 branch August 6, 2026 22:34
williamthorsen added a commit that referenced this pull request Aug 7, 2026
…ssembly-mcp-v0.2.4 codeassembly-v0.6.0

codeassembly-v0.6.0
- #794 feat: Guide kb-add note placement with the store's declared taxonomy (#1223)
- #1111 feat: Declare which harnesses sync targets (#1221)
- #1214 feat: Allow an artifact to belong to more than one collection (#1219)
- #1186 drop!: Dissolve `common-mistakes` into its consumers' guidance (#1217)
- deps: Upgrade all deps to latest version
- #1203 fix: Bar acceptance-criteria revision prompts to genuine conflicts (#1212)
- #1196 feat: Add the .kb/taxonomy.yaml format with drift reporting and back-fill (#1210)
- #1165 feat: Make a vetted subset of the library declarable (#1207)
- #1197 feat: Report the retired `harnesses:` frontmatter key from `validate` (#1204)
- #963 feat!: Rename the harness id to rovo and qualify the frontmatter key (#1199)
- #1188 drop!: Retire the bundled Bitbucket inline-comment script (#1198)
- #1187 feat: Gate the boolean prefix on a test and add the tail rule it rests on (#1195)

factory-v0.2.4
- deps: Upgrade all deps to latest version
- deps: Upgrade all deps to latest version
- #1209 refactor: Clear factory's remaining lint violations and retire both deferral lists (#1216)
- #1208 refactor: Clear the visualizations lint violations and retire six rule deferrals (#1213)

kb-v0.4.0
- #794 feat: Guide kb-add note placement with the store's declared taxonomy (#1223)
- #1196 feat: Add the .kb/taxonomy.yaml format with drift reporting and back-fill (#1210)

codeassembly-lifecycle-v0.3.0
- #963 feat!: Rename the harness id to rovo and qualify the frontmatter key (#1199)

codeassembly-mcp-v0.2.4
- #1206 refactor: Retire fleet's lint deferrals (#1211)
- #1200 tooling: Retire mcp's lint deferrals and prune the dead root entries (#1205)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Added or improved external functionality scope:agents

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Declare which harnesses sync targets

1 participant