Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 80 additions & 24 deletions docs/setup/agentic-overrides.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [Agentic overrides — modifying framework workflows in an adopter](#agentic-overrides--modifying-framework-workflows-in-an-adopter)
- [Where override files live](#where-override-files-live)
- [Override surfaces — two directories, one lookup chain](#override-surfaces--two-directories-one-lookup-chain)
- [Adoption and `.gitignore`](#adoption-and-gitignore)
- [What an override file may contain](#what-an-override-file-may-contain)
- [Skip a step](#skip-a-step)
- [Replace a step](#replace-a-step)
Expand Down Expand Up @@ -40,19 +41,52 @@ run-time and applies before executing default behaviour.
This document is the **contract** between adopter authors of
override files and framework authors of skills that read them.

## Where override files live
## Override surfaces — two directories, one lookup chain

Framework skills consult **two** override directories in precedence
order, first hit wins:

1. **`.apache-magpie-local/<skill>.md`** — personal, gitignored.
Per-developer preferences (local clone paths, a release manager
enabling an extra MCP, wording adjustments) that should not be
committed to the shared repo. Never committed; never pushed.
Works on a repo that has not yet adopted Magpie — the user adds
one `.gitignore` line by hand so the directory stays untracked,
then drops their overrides there. The same additive-only
guardrail applies: it cannot weaken the safety, confidentiality,
or privacy baseline.

2. **`.apache-magpie-overrides/<skill>.md`** — committed,
project-wide. Shared modifications every contributor on the
project sees (custom steps, project-specific defaults, integrations
with project tooling).

```text
<adopter-repo>/.apache-magpie-overrides/
├── README.md (the dir's own readme,
│ scaffolded by
│ /magpie-setup adopt)
├── <framework-skill-name>.md (e.g. pr-management-triage.md)
└── <other-framework-skill-name>.md
<adopter-repo>/
├── .apache-magpie-local/ (gitignored, per-person)
│ └── <framework-skill-name>.md (e.g. pr-management-triage.md)
├── .apache-magpie-overrides/ (committed, project-wide)
│ ├── README.md (scaffolded by /magpie-setup adopt)
│ ├── <framework-skill-name>.md
│ └── <other-framework-skill-name>.md
```

The directory is **committed** in the adopter repo (the whole
point is for overrides to ship with the project's repo).
When both directories contain a file for the same skill, the
personal-local file wins — its instructions are applied first,
then (by default) the committed file's instructions are also applied
unless the personal file explicitly says to skip it. Neither file
is required to exist; a skill that finds neither proceeds with
framework defaults.

### Adoption and `.gitignore`

`/magpie-setup adopt` adds `/.apache-magpie-local/` to the adopter
repo's `.gitignore` automatically. On a repo that has not adopted
Magpie, add the line manually:

```text
/.apache-magpie-local/
```

## What an override file may contain

Expand Down Expand Up @@ -128,17 +162,24 @@ maintainer (or a future agent on a later run):
Every framework skill that supports overrides starts each
invocation with this opening protocol:

1. Read `<adopter-repo>/.apache-magpie-overrides/<this-skill>.md`
if it exists. Surface the file's title and the list of
override headlines (`### Override N — ...`) to the user
before doing anything else.
2. Apply the overrides: each `### Override N — ...` section
modifies the skill's default behaviour for this run. The
agent interprets the instructions in the override section
and adjusts the rest of the skill's flow accordingly.
3. After the skill finishes, recap which overrides were
applied (and any the agent decided not to apply with the
reasoning), so the user has an audit trail.
1. Read `<adopter-repo>/.apache-magpie-local/<this-skill>.md`
(personal, gitignored) if it exists.
2. Read `<adopter-repo>/.apache-magpie-overrides/<this-skill>.md`
(committed, project-wide) if it exists.
3. Surface the titles and override headlines
(`### Override N — ...`) from both files to the user
before doing anything else. Indicate which file each
came from so the user can tell personal from shared
overrides at a glance.
4. Apply both sets of overrides: personal-local first, then
committed. Each `### Override N — ...` section modifies
the skill's default behaviour for this run. The agent
interprets the instructions and adjusts the rest of the
skill's flow accordingly.
5. After the skill finishes, recap which overrides were
applied (source file + override headline), and any the
agent decided not to apply with the reasoning, so the user
has an audit trail.

A skill that does **not** yet support overrides documents
that explicitly in its `SKILL.md`. The
Expand All @@ -155,7 +196,12 @@ A framework agent NEVER:
`<adopter-repo>/.apache-magpie/`. The snapshot is a build
artefact — every modification gets blown away on the next
`/magpie-setup upgrade`. Local mods go into
`.apache-magpie-overrides/`.
`.apache-magpie-local/` (personal) or
`.apache-magpie-overrides/` (shared).
- Commits or pushes `.apache-magpie-local/` content. The
personal override directory is gitignored by design — it
carries per-person paths, credentials, and capability
enablements the contributor has not chosen to share.
- Proposes overrides be merged in by editing the framework
source in the snapshot. Framework changes go via PR to
`apache/magpie`.
Expand All @@ -165,6 +211,9 @@ A framework agent NEVER:
human decide (the override expresses adopter intent —
re-anchoring it correctly is human judgement, not
pattern-matching).
- Weakens the safety, confidentiality, or privacy baseline
from either override surface. An override that attempts
to do so is ignored and the conflict is surfaced.

## Reconciliation on framework upgrade

Expand Down Expand Up @@ -246,6 +295,13 @@ this by:

## Cross-references

- [`setup` skill](../../skills/setup/SKILL.md) — the entry point that manages the snapshot + scaffolds overrides.
- [`overrides.md` sub-action](../../skills/setup/overrides.md) — interactive override creation.
- [`setup` skill](../../skills/setup/SKILL.md) — the entry point
that manages the snapshot, scaffolds overrides, and adds the
`.gitignore` entries for both override directories.
- [`overrides.md` sub-action](../../skills/setup/overrides.md) —
interactive override creation (lets the user choose between the
personal-local and committed surfaces).
- [Top-level README](../../README.md) — adoption flow.
- [`setup-status` skill](../../skills/setup-status/SKILL.md) —
the adoption dashboard, which reports whether both override
directories are present.
13 changes: 8 additions & 5 deletions skills/setup-status/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,15 @@ it rather than duplicating its checks.
## Adopter overrides

Before running the default behaviour documented below, this skill
consults
[`.apache-magpie-overrides/setup-status.md`](../../docs/setup/agentic-overrides.md)
in the adopter repo if it exists, and applies any agent-readable
overrides it finds. See
consults (in order, first hit wins):

1. `.apache-magpie-local/setup-status.md` — personal, gitignored.
2. `.apache-magpie-overrides/setup-status.md` — committed,
project-wide.

Both files are applied if present. See
[`docs/setup/agentic-overrides.md`](../../docs/setup/agentic-overrides.md)
for the contract.
for the full lookup contract.

**Hard rule**: agents NEVER modify the snapshot under
`<adopter-repo>/.apache-magpie/`. Local modifications go in the
Expand Down
7 changes: 5 additions & 2 deletions skills/setup-status/collect.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ python3 <framework>/skills/setup-status/scripts/collect_status.py --format json
| `agent_targets` | One record per registry target (see below). |
| `active_target_ids` | The subset of registry ids whose directory is present on disk. |
| `families` | The installed-skill roster grouped by family (see below). |
| `overrides` | `{present, has_readme}` for `.apache-magpie-overrides/`. |
| `overrides` | `{present, has_readme, skill_count}` for `.apache-magpie-overrides/` (committed, shared). |
| `local_overrides` | `{present, has_readme, skill_count}` for `.apache-magpie-local/` (gitignored, personal). Always reported; `present: false` when the directory does not exist. |
| `post_checkout_hook` | `{present, executable, has_verify_recipe}`. |
| `gitignore` | Coverage flags (see below). |

Expand Down Expand Up @@ -106,7 +107,9 @@ for the *intended* set, and use the on-disk read for the
## `gitignore`

Top-level flags (`snapshot_ignored`, `local_lock_ignored`,
`settings_local_ignored`) plus a per-target map. Each target
`local_overrides_ignored`, `settings_local_ignored`) plus a
per-target map. `local_overrides_ignored` is `true` when
`/.apache-magpie-local/` appears in `.gitignore`. Each target
carries `glob_ignored` + `setup_unignored` (the **normal-adopter**
pattern: ignore the symlinks, keep the bootstrap tracked) and
`all_unignored` (the **self-adoption** pattern: every `magpie-*`
Expand Down
5 changes: 4 additions & 1 deletion skills/setup-status/render.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ security ✅ 12 · pr-management ✅ 8 · issue ✅ 8 · release-management ✅
### Drift & integrity

- **drift:** n/a (method:local …) · **snapshot:** in-repo source (local)
- **overrides:** — · **hook:** —
- **shared overrides** (`.apache-magpie-overrides/`): — · **personal overrides** (`.apache-magpie-local/`): —
- **hook:** —
- → deep check (integrity, permissions, worktrees): `/magpie-setup verify`
```

Expand Down Expand Up @@ -107,6 +108,8 @@ this before assigning health:
| `gitignore.targets[].all_unignored` | ✅ expected — symlinks are committed | not the pattern used; ignore |
| `gitignore.targets[].glob_ignored` + `setup_unignored` | not used | ✅ expected — symlinks gitignored, bootstrap tracked |
| `drift.checked == false` | ✅ nothing to drift against | depends on `reason` (see [`collect.md`](collect.md#drift)) |
| `local_overrides.present == false` | ✅ optional personal surface — not required | ✅ same — `.apache-magpie-local/` is always optional |
| `gitignore.local_overrides_ignored == false` | advisory: add `/.apache-magpie-local/` to `.gitignore` | same advisory |

Never report a self-adopted framework checkout as unhealthy merely
for lacking a snapshot, a local lock, or ignored symlinks — those
Expand Down
47 changes: 37 additions & 10 deletions skills/setup-status/scripts/collect_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@

Read-only. Enumerates the on-disk adoption artefacts —
the two lock files, the framework-skill symlinks across every
agent target, the snapshot, the overrides directory, the
post-checkout hook, and the .gitignore coverage — and emits a
single JSON document the ``setup-status`` skill renders into a
dashboard.
agent target, the snapshot, the overrides directories (both the
committed .apache-magpie-overrides/ and the personal
.apache-magpie-local/), the post-checkout hook, and the
.gitignore coverage — and emits a single JSON document the
``setup-status`` skill renders into a dashboard.

The script never fetches over the network and never writes: the
upstream-tip drift check and any remediation belong to
Expand Down Expand Up @@ -302,6 +303,7 @@ def gitignore_coverage(root: Path, targets: list[dict]) -> dict:
"present": gi.is_file(),
"snapshot_ignored": "/.apache-magpie/" in lines,
"local_lock_ignored": "/.apache-magpie.local.lock" in lines,
"local_overrides_ignored": "/.apache-magpie-local/" in lines,
"settings_local_ignored": "/.claude/settings.local.json" in lines,
"targets": {},
}
Expand All @@ -323,6 +325,22 @@ def gitignore_coverage(root: Path, targets: list[dict]) -> dict:
return cov


def override_dir_status(root: Path, dirname: str) -> dict:
"""Describe one override directory (committed or personal-local)."""
d = root / dirname
if not d.is_dir():
return {"present": False, "has_readme": False, "skill_count": 0}
skill_files = [
p for p in d.iterdir()
if p.is_file() and p.suffix == ".md" and p.name != "README.md"
]
return {
"present": True,
"has_readme": (d / "README.md").is_file(),
"skill_count": len(skill_files),
}


def hook_status(root: Path) -> dict:
hook = root / ".git" / "hooks" / "post-checkout"
if not hook.is_file():
Expand Down Expand Up @@ -435,9 +453,20 @@ def render_markdown(d: dict) -> str:
out.append("### Drift & integrity")
out.append("")
out.append(f"- **drift:** {drift_line} · **snapshot:** {snap}")
ov = d["overrides"]
local_ov = d["local_overrides"]
ov_text = f"present ({ov['skill_count']} skill(s))" if ov["present"] else "—"
local_ov_text = (
f"present ({local_ov['skill_count']} skill(s))"
if local_ov["present"]
else "—"
)
out.append(
f"- **overrides:** {'present' if d['overrides']['present'] else '—'} · "
f"**hook:** {'installed' if d['post_checkout_hook']['present'] else '—'}"
f"- **shared overrides** (`.apache-magpie-overrides/`): {ov_text} · "
f"**personal overrides** (`.apache-magpie-local/`): {local_ov_text}"
)
out.append(
f"- **hook:** {'installed' if d['post_checkout_hook']['present'] else '—'}"
)
out.append("- → deep check (integrity, permissions, worktrees): `/magpie-setup verify`")
return "\n".join(out) + "\n"
Expand Down Expand Up @@ -485,10 +514,8 @@ def main(argv: list[str] | None = None) -> int:
"agent_targets": targets,
"active_target_ids": [t["id"] for t in targets if t["present"]],
"families": families_installed(canonical["entries"]),
"overrides": {
"present": (root / ".apache-magpie-overrides").is_dir(),
"has_readme": (root / ".apache-magpie-overrides" / "README.md").is_file(),
},
"overrides": override_dir_status(root, ".apache-magpie-overrides"),
"local_overrides": override_dir_status(root, ".apache-magpie-local"),
"post_checkout_hook": hook_status(root),
"gitignore": gitignore_coverage(root, targets),
}
Expand Down
22 changes: 16 additions & 6 deletions skills/setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ Gitignored in the adopter repo:
- `<snapshot-dir>` (the entire framework snapshot — gigabytes
potentially).
- `<local-lock>` (per-machine state).
- `.apache-magpie-local/` (personal, per-developer override
directory — see Golden rule 7).
- The `magpie-*` symlinks `setup adopt` creates in every active
target dir — the canonical ones in `.agents/skills/` (they
target the gitignored snapshot) and the relays in
Expand Down Expand Up @@ -298,13 +300,21 @@ silently mis-applies.

**Golden rule 7 — agentic overrides are read at run-time.**
Every framework skill that supports overrides starts its run
by checking `.apache-magpie-overrides/<this-skill>.md` for
adopter-specific instructions and applying them before
executing the default behaviour. The override file is plain
markdown the agent interprets — no templating engine, no
patch tool. See
by consulting **two** directories in precedence order (first
hit wins):

1. `.apache-magpie-local/<this-skill>.md` — personal,
gitignored. Per-developer overrides that are never
committed.
2. `.apache-magpie-overrides/<this-skill>.md` — committed,
project-wide. Overrides shared with every contributor.

Both files are plain markdown the agent interprets — no
templating engine, no patch tool. The additive-only guardrail
applies to both: neither may weaken the framework's safety,
confidentiality, or privacy baseline. See
[`docs/setup/agentic-overrides.md`](../../docs/setup/agentic-overrides.md)
for the contract.
for the full contract including the lookup protocol.

**Golden rule 8 — family membership is declared in
frontmatter; two families are *always* installed, the rest
Expand Down
17 changes: 17 additions & 0 deletions skills/setup/adopt.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ idempotent — re-add them if they're missing.
```text
/.apache-magpie/
/.apache-magpie.local.lock
/.apache-magpie-local/
/.apache-magpie-sources/
/.apache-magpie.sources.local.lock
/.claude/settings.local.json
Expand Down Expand Up @@ -757,11 +758,27 @@ in the framework for the full contract.
**Hard rule**: never modify the snapshot under
`<repo-root>/.apache-magpie/`. Local mods go here.
Framework changes go via PR to `apache/magpie`.

**Personal (non-shared) overrides** belong in
`<repo-root>/.apache-magpie-local/` (gitignored). Use that
directory for per-developer paths, local tooling, or role-
specific capability enablements you do not want committed
to this repo.
```

This directory is **committed** (overrides ship with the
adopter repo).

Tell the user about the personal override surface:

> *"`.apache-magpie-local/` is gitignored (already in
> `.gitignore`). Create it at any time for per-person
> overrides that should not be committed — capability
> enablements, local clone paths, wording you want only
> for yourself. The framework reads it before the
> committed `.apache-magpie-overrides/` on every skill
> invocation."*

## Step 9b — Scaffold `user.md` (FRESH only)

Create the operator's per-user configuration file. The security
Expand Down
Loading