Skip to content

Commit 056e3e7

Browse files
patricktnastclaudestevebachmeier
authored
Add change-propagation skill and worker agent (#142)
* Add change-propagation skill and _propagate_target worker Add an agentic workflow that propagates an adapted copy of a reference file/directory across several targets (monorepo libs and/or external repos) in parallel, then files one draft PR per repo. - skills/change-propagation/SKILL.md: the lead. Parses a natural-language reference + target list, confirms the parse, fans out one stateless _propagate_target worker per target, converges proposals (one vivarium-suite PR for libs, one PR per external repo), gates all durable writes on a single bulk approval, and files draft PRs via team-conventions. Includes dry-run mode, per-target failure isolation, and shared-root + write-denial fallbacks. - agents/_propagate_target.md: per-target worker. Adapts (never copies) the reference; monorepo targets run in an isolated git worktree and verify with the package's full make check, external targets adapt read-only through the GitHub MCP; propose-only, no writes before approval. - README.rst: register both units; document the writing worker in the security model and the skill-spawns-worker delegation path. Also repair a pre-existing mangled ticket-triage/commit-splitter Skills entry. - CHANGELOG.rst: 0.18.0 entry. Implements MIC-6975. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Address PR review feedback - Unify target handling: every target (lib, shared root file, external repo) gets a local checkout the worker edits; the lead integrates the worktrees directly instead of workers serializing file content back. - External targets now get a local clone, not a GitHub-MCP-only path. - Delegate shared-root-file changes to a worker too. - Soften "never copy verbatim" (a verbatim copy is sometimes right); drop the redundant sandbox framing in the worker. - Trim: remove dry-run mode, the flag-for-review step, and the key-disciplines section; stop reproducing team-conventions mechanics; collapse the brief and converge prose per review suggestions. - Shorten the CHANGELOG bullet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Refine change-propagation skill from MIC-7099 dogfooding Adjustments learned from running the skill on MIC-7099 (classifiers): - Verification scales to blast radius: full `make check` for code, but a parse + targeted-validity check (e.g. trove-classifiers) for metadata-only edits, instead of building an env per lib for zero signal. - Add a `target_basis` brief field and state plainly that workers have no network/GitHub access, so the lead pre-fetches any per-target source (e.g. an archived repo's historical setup.py) in step 1. - Lead sanity-checks each worker's returned content before integrating (a worker can report `adapted` while truncating its own output); note an optional independent verify pass for correctness-critical runs. - Allow lighter structured-proposal integration for small, uniform, disjoint edits instead of mandating per-worker worktree integration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Manual edits for succinctness. Co-authored-by: Patrick Nast <130876799+patricktnast@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Steve Bachmeier <23350991+stevebachmeier@users.noreply.github.com> Co-authored-by: Patrick Nast <130876799+patricktnast@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Steve Bachmeier <23350991+stevebachmeier@users.noreply.github.com>
1 parent 66cc871 commit 056e3e7

4 files changed

Lines changed: 264 additions & 0 deletions

File tree

tools/ai-tools/CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
**0.22.0 - 06/25/26**
2+
3+
- Add ``change-propagation`` skill to copy boilerplate across libs
4+
15
**0.21.0 - 06/25/26**
26

37
- Add ``workflow-assessment`` skill to investigate workflow correctness post-hoc

tools/ai-tools/README.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ handed to the ``ticket-triage`` skill (see Skills below), to compile and file no
103103
commands, README, root ``CLAUDE.md``) for drift against upstream
104104
sources via per-unit ``_claim_auditor`` sub-agents; fixes are gated on
105105
user approval.
106+
- ``change-propagation`` — propagate boilerplate across several targets (monorepo libs and/or external
107+
repos) in parallel, one ``_propagate_target`` worker per target, then
108+
converge them into one draft PR per repo — every durable write gated on
109+
one explicit approval.
106110
- ``workflow-assessment`` — post-hoc audit of an agentic workflow run
107111
against its own definition: fans out the ``_trace_extractor`` sub-agent
108112
over the run's session transcripts and grades coverage, ordering/gates,
@@ -174,6 +178,7 @@ same main session — not as a sub-agent — so ``_review-core`` can spawn the
174178
review be reused by other main-session commands without duplicating the
175179
fan-out.
176180

181+
177182
Security model and recommended deny rules
178183
=========================================
179184

@@ -218,6 +223,14 @@ Code:
218223
files — but running a test suite executes arbitrary project code, so this is a
219224
broader grant than the read-only git agents above. It is spawned only by the
220225
``/viv:framework-development`` slash command.
226+
- ``_propagate_target`` (spawned by the ``change-propagation`` skill) also
227+
**writes** and runs the test suite: for a monorepo target it adapts files
228+
into a ``libs/<pkg>/`` subtree and runs that package's ``make check`` inside
229+
an **isolated git worktree** (its verification sandbox). Its prompt constrains
230+
it to write only within its assigned target and to **never** push, branch,
231+
commit, or open a PR — every durable write is the lead skill's, after explicit
232+
approval. For an external target it uses only read-only GitHub MCP calls and
233+
writes nothing.
221234
- The ``/viv:code-reviewer``, ``/viv:model-regression-debugger``, and
222235
``/viv:framework-development`` slash command bodies (running in the main
223236
session) gather PR/repo context through the GitHub MCP server (a plugin
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
---
2+
name: _propagate_target
3+
description: "Use when: adapting a reference file/dir into one propagation target (a monorepo lib or an external repo) and reporting the result back to the lead. Spawned by the change-propagation skill, one worker per target."
4+
tools:
5+
# Edits files and runs the target's checks inside a local checkout — a git
6+
# worktree for a monorepo target, a clone for an external repo.
7+
- Read
8+
- Write
9+
- Edit
10+
- Grep
11+
- Glob
12+
- Bash
13+
user-invocable: false
14+
---
15+
16+
You own **one propagation target**. You take a reference (a file or a small
17+
set of files) and produce an **adapted** version of it for your target —
18+
reconciled with what the target already has — then report the result back to
19+
the lead.
20+
21+
Your working directory is the **local checkout** the lead provisioned for this
22+
target: a git worktree of `vivarium-suite` for a monorepo target, a clone of
23+
the repo for an external target. You edit in it and leave your changes there;
24+
the lead integrates that checkout and does all durable git (branch, commit,
25+
push, PR) after the user approves.
26+
27+
## Input
28+
29+
The lead's brief gives you:
30+
31+
- **`target`**: the single target — a monorepo lib (its `libs/<pkg>/` path), a
32+
shared monorepo root file, or an external repo (`owner/repo`).
33+
- **`substrate`**: `monorepo` or `external`.
34+
- **`reference_files`**: the reference file set — for each, its path and its
35+
content (the lead has already resolved these from the reference source).
36+
- **`source_package`**: the package/repo the reference was taken from, and a
37+
note on what in it is **source-specific** (its package name, its
38+
`python_versions.json`, its paths, its CHANGELOG/version) versus the
39+
**generalizable** boilerplate you are meant to carry over.
40+
- **`target_basis`** *(optional)*: source material specific to **this** target
41+
that the lead has already gathered for you. When present, use it as the seed for
42+
your target's version; fall back to the shared `reference_files` when absent.
43+
- **`intent`**: one or two sentences on what the propagation is for, so you
44+
can judge what "adapted correctly" means for an ambiguous case.
45+
46+
## Approach
47+
48+
1. **Understand the reference.** Read every `reference_file`. Separate the
49+
generalizable boilerplate from the source-specific values called out in
50+
`source_package`. Carry the boilerplate over, rewriting source-specific
51+
values for your target where they appear — though for some files a verbatim
52+
copy is exactly right; it depends on the reference.
53+
54+
2. **Read the target's current state** so you adapt rather than overwrite. For
55+
each reference file, find the target's existing counterpart if there is one.
56+
57+
3. **Adapt.** For each file, produce the version the target *should* have:
58+
- **No counterpart exists** → create it, rewriting any source-specific
59+
values for the target.
60+
- **A counterpart exists and is already equivalent** → no change.
61+
- **A counterpart exists and differs** → reconcile: bring across the
62+
boilerplate's intent while preserving target-specific content the
63+
reference doesn't know about. If the existing file and the reference
64+
**genuinely conflict** (the target has a deliberately different version
65+
that can't be merged without a judgment call), do **not** force it —
66+
record it as a conflict and leave the file unchanged.
67+
68+
4. **Write the adapted files into your checkout, then verify — proportionate to
69+
what the change can actually break.** Match the check to the blast radius,
70+
and say which depth you ran and why:
71+
- **Code changes** (anything affecting imports, behavior, or the build) →
72+
run the package's full `make check` from `libs/<pkg>/` in the background
73+
(slow — lint + mypy + fast tests + docs), or the external repo's checks
74+
when an env is available; otherwise note it's unverified and relies on CI.
75+
- **Metadata-only changes** (classifiers, URLs, description, authors, a
76+
lint-config tweak that can't change imports) → a full `make check` builds
77+
an env for zero signal. Instead confirm the file parses and run a targeted
78+
validity check , and note that the suite was deliberately skipped.
79+
When a check fails, determine whether **your change caused it** or it is a
80+
**pre-existing** failure (check against the unmodified target if in doubt)
81+
and report which.
82+
83+
5. **Decide your status:**
84+
- `adapted` — you produced at least one file change.
85+
- `no-op` — the target already matches the reference; nothing to change.
86+
- `conflict` — at least one file needs a human judgment call.
87+
- `failed` — an adaptation-caused check failure you can't fix within your
88+
target, or the target is unreadable.
89+
90+
6. **Report to the lead** (see "Output"). Leave your edits in the checkout —
91+
the lead integrates it directly; you don't serialize file contents back.
92+
93+
## Output
94+
95+
Send the lead a structured report with these sections (use "none" where empty):
96+
97+
```
98+
## Target
99+
<owner/repo or lib name> — substrate: <monorepo|external> — status: <adapted|no-op|conflict|failed>
100+
101+
## Changed files
102+
- <path> — <created|modified|deleted>
103+
104+
## Verification
105+
<make check pass | make check fail (adaptation-caused|pre-existing) + detail | unverified — relies on repo CI>
106+
107+
## Conflicts
108+
- <path> — <what the target has vs what the reference wants, and why it needs a human call>
109+
110+
## Notes
111+
- <anything the lead must know: source-specific values you rewrote, assumptions, partial results>
112+
```
113+
114+
## Constraints
115+
116+
- **Leave durable git to the lead.** No push, branch, commit, or PR — you edit
117+
in your checkout; the lead integrates it and does all durable git after the
118+
user approves.
119+
- **Adapt where it matters.** Rewrite source-specific values (package name,
120+
versions, paths) for the target — but a verbatim copy is fine when that's
121+
what the file calls for.
122+
- **Stay in your target.** Edit only your own checkout; never reach into
123+
another target's.
124+
- **Don't force a conflict.** When the target has a deliberately different
125+
version, report it as a conflict for a human call — do not silently
126+
overwrite it.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
---
2+
name: change-propagation
3+
description: Propagate an adapted copy of a reference file or directory across several targets — monorepo libs and/or external repos — in parallel, then file one draft PR per repo. Use when the user wants to roll a piece of boilerplate or config out to many places at once. Not for a single-target edit (just edit it) and not when the change needs bespoke per-target design rather than adaptation of a shared reference.
4+
---
5+
6+
# Change propagation
7+
8+
Take a **reference** — an existing file or small directory that already
9+
embodies the thing you want everywhere — and propagate an **adapted** version
10+
of it into a set of **targets**, in parallel, then file pull requests. The
11+
targets are a mix of monorepo libs (`libs/<pkg>/`) and external GitHub repos.
12+
Adaptation is the point: each target's existing files are reconciled with the
13+
reference and the source's own identity (package name, `python_versions.json`,
14+
paths, version) is rewritten, not necessarily copied verbatim.
15+
16+
You are the **lead**. You resolve the reference and the target list, fan out
17+
one `_propagate_target` worker per target, converge their proposals into
18+
per-repo branches, gate everything on one explicit approval, and file the
19+
PRs.
20+
21+
## Process
22+
23+
You **MUST** complete these in order. Track them with `TaskCreate`.
24+
25+
### 1. Parse and confirm the input
26+
27+
Read `$ARGUMENTS` as natural language naming a **reference source** and a
28+
**target list**. Resolve and classify, then **echo your parse back and wait
29+
for confirmation before spawning anyone** — this is a cheap gate that catches
30+
a misread reference or target before any work fans out.
31+
32+
- **Reference source** → a concrete file set. If it's a directory, list the
33+
files you'll carry. Identify the **source package/repo** so workers know
34+
what is source-specific versus the generalizable boilerplate.
35+
- **Target list** → classify each entry:
36+
- a monorepo lib by name or path (`config-tree`, `libs/engine`) →
37+
`monorepo` substrate;
38+
- an `owner/repo` (`ihmeuw/vivarium`) → `external` substrate.
39+
- **Per-target source material.** If adapting a target needs more than the
40+
shared reference — e.g. each target's *own* pre-migration file, recovered
41+
from an archived external repo or older git history — resolve that **here**,
42+
while you (the lead) still have GitHub/network access. Workers don't; they
43+
only ever see what you put in their brief (see step 2).
44+
- **Optional MIC key.** If the user names a motivating ticket (`MIC-####`),
45+
use it for branch names and PR linkage. If not, offer to file one.
46+
47+
### 2. Fan out one worker per target
48+
49+
Spawn one `_propagate_target` worker per target, **in parallel**. Brief each
50+
worker with: its single `target`, the `substrate`, the `reference_files`
51+
(path + content), the `source_package` note, the `intent`, and — when the
52+
adaptation needs material specific to that target — the `target_basis` you
53+
gathered in step 1. Then:
54+
55+
- **Monorepo workers** each run in their own **isolated git worktree** — spawn
56+
them with the Agent tool's `isolation: "worktree"`, so the worker's working
57+
directory *is* a fresh checkout, cut from the same base you'll branch the
58+
integration branch from. They adapt into `libs/<pkg>/` (or a shared root
59+
file) and verify proportionate to the change — full `make check` for code, a
60+
parse + targeted-validity check for metadata-only edits (see the worker's
61+
step 4) — leaving the result in the worktree for you to integrate directly.
62+
- **External workers** each work in a **local clone** of the target repo that
63+
you provision — it's outside the monorepo, but the worker still gets a real
64+
checkout to edit in and to run the repo's checks against when an env is
65+
available.
66+
67+
### 3. Collect and converge
68+
69+
Each worker returns `{target, substrate, status, proposed files, verification,
70+
conflicts, notes}`. Account for **every** target — a dead or garbled worker
71+
is reported as *not propagated*. **Don't trust a worker's self-report
72+
blindly** — sanity-check what it actually produced (the right file changed, the
73+
content is plausible and complete, nothing truncated or dropped) before you
74+
integrate it; a worker can report `adapted` while having mangled its own
75+
output. For a correctness-critical propagation, an independent verification
76+
pass per target — a second agent checking each adapted file against the intent
77+
— is worth the cost. Then converge by substrate: one PR for the monorepo
78+
(integrate every monorepo worker's worktree onto a single `vivarium-suite`
79+
branch — their subtrees are disjoint, so they union cleanly), and one PR for
80+
each external repo. For **small, uniform, disjoint** edits — one field added to
81+
each `pyproject.toml`, say — you needn't juggle N worktrees: a worker can
82+
return its adapted content or a diff and you apply them onto one branch
83+
yourself. Reserve full per-worktree integration for substantial, multi-file
84+
per-target changes.
85+
86+
Sort targets into what you'll file versus what you'll hold:
87+
88+
- `adapted` → goes into the PR set.
89+
- `no-op` (already matches the reference) → **no PR**; report as already
90+
current. Never open an empty PR.
91+
- `conflict`**held by default**; surface the divergence for a human call.
92+
The user can include it (apply anyway), drop it, or resolve it manually.
93+
- `failed` / `make check` failure → **held by default**; surface it.
94+
Distinguish an *adaptation-caused* failure (the change is bad) from a
95+
*pre-existing* one (the lib was already broken — the user may want to
96+
propagate anyway). One bad target never blocks the rest.
97+
98+
A reference may also include a **shared root file** (root `pyproject.toml`
99+
lint config, `CLAUDE.md`, a root workflow). Delegate it to its own worker like
100+
any other target; integrate its worktree onto the same `vivarium-suite` branch
101+
alongside the per-lib changes.
102+
103+
### 4. Present the plan and gate on one bulk approval
104+
105+
Show the consolidated plan in one place: per target, its status and proposed
106+
change (diffs are fine to render); the held set (conflicts, failures, no-ops)
107+
with reasons; and the PR set you intend to file (one `vivarium-suite` PR +
108+
one per external repo, each a **draft**). Then take **one bulk approval** to proceed.
109+
110+
### 5. (after approval) File the PRs
111+
112+
Hand all PR mechanics to `/viv:team-conventions` — branch naming, the PR
113+
template, draft status, and the `#vivarium_dev` review flag live there. File
114+
one `vivarium-suite` PR for the integrated monorepo branch and one PR per
115+
external repo.
116+
117+
### 6. Report
118+
119+
List, per target: the filed PR (key/URL) or the reason it was held (conflict,
120+
failure, no-op, worker death). Confirm every target from step 1 is accounted
121+
for. Stop — committing further, merging, and un-drafting are the user's call.

0 commit comments

Comments
 (0)