How it works¶
This page explains the architecture for people who want to know why the plugin behaves the way it does. You don't need to read it to use the plugin.
One plugin, many organisations¶
Every organisation in the EMPN network installs the same canonical plugin from the same GitHub repository. The differentiation — voice, workspaces, visuals, source tiers, folder conventions — lives in a per-organisation overlay folder inside the plugin.
flowchart LR
R[EMPN-eu/empn-core<br/>GitHub repo] --> C[Cowork sync]
C --> A[Arena Idé<br/>install]
C --> D[Dezernat Zukunft<br/>install]
C --> I[Institut Avant-Garde<br/>install]
C --> E[EMPN<br/>install]
A -->|org: arena-ide| O1[arena-ide overlay]
D -->|org: dezernat-zukunft| O2[dezernat-zukunft overlay]
I -->|org: institut-avant-garde| O3[institut-avant-garde overlay]
E -->|org: empn| O4[empn overlay]
Same skills run everywhere. Outputs differ because the shared docs they load resolve through the active overlay.
The lookup chain¶
When a skill needs a shared doc — house style, the workspace list, design tokens, folder conventions, anything — it loads shared/<topic> and the resolver walks three layers in order:
shared/<org>/<topic> ← partner override (if exists)
shared/_common/<topic> ← universal rules
shared/empn/<topic> ← canonical reference + default fallback
First match wins. A partner overlay is sparse by design: it only contains files the partner wants to differ from EMPN's defaults. Everything else falls through.
flowchart LR
Skill["skill calls<br/>shared/house-style.md"] --> Resolver{lookup<br/>chain}
Resolver -->|exists?| Partner["shared/<org>/<br/>house-style.md"]
Resolver -->|exists?| Common["shared/_common/<br/>house-style.md"]
Resolver -->|fallback| Empn["shared/empn/<br/>house-style.md"]
Partner --> Return["return file"]
Common --> Return
Empn --> Return
A partner with no override gets EMPN-branded outputs on Day 1 — a working starting point, not an empty one.
What goes where¶
| Layer | What lives there | Examples |
|---|---|---|
_common/ |
Universal across the network — methodology that doesn't change per org | anti-ai-tone.md, search-pattern.md, monitor-pattern.md, eu-legislation-process.md, packs/energy/data-tiers.md |
empn/ |
EMPN's identity, also the default fallback for partners who don't override | house-style.md, workspaces.md, folder-conventions.md, org-identity.md, visuals/, narrative-arc.md |
<partner>/ |
A partner's overrides — sparse | Usually org-identity.md, visuals/, workspaces.md, sometimes house-style.md |
For the file-by-file split, see Folder structure.
The resolver itself¶
shared/scripts/setup_org.py --resolve runs at the start of every session, and again at the top of the thirteen skills that put a logo or a contact line on the page. It:
- Reads the
org:line from the workspaceCLAUDE.md— the file the agent already reads first, so nothing has to be staged and no environment variable is involved. When the script cannot see that file (a cloud session runs the plugin in a container with no access to the vault), the agent passes what it read as--org <slug>. - Validates the slug against
shared/_orgs.yaml. Refuses unregistered slugs with a clear error and never guesses a near-match — a one-character typo would otherwise put one organisation's brand on another's output. - Prints the active org and the ordered search path, e.g.
EMPN_SEARCH_PATH dezernat-zukunft _common empn. The caller reads eachshared/<file>as the first hit along that path. It writes nothing, anywhere.
A workspace with no org: line resolves to empn and says so, every session. That is the one place the design accepts a default, and it is a loud one: the session proceeds (a partner with an unconfigured vault still gets a working plugin) and the response has to tell the user which identity is in use and offer to record theirs. Recording it ends the reminder.
--ensure does the same resolution and additionally creates symlinks at the canonical names (shared/house-style.md, shared/visuals/, …) so a plain markdown link resolves on the filesystem. That is a local convenience for Claude Code and the repo's own tests, not the mechanism — the plugin directory is re-synced from git on every push and may be read-only in Cowork.
The model never invents the org. It either reads a declared slug or reports that none exists.
Why an overlay model instead of forking¶
The obvious alternative is "every partner forks the repo." That fails in two ways for this audience:
- Most partner contacts don't maintain a git fork comfortably. Pulling upstream changes into a forked
shared/runs into merge conflicts the first time, and never gets fixed. - Improvements to the universal layer (search patterns, EU process docs) should propagate to everyone automatically. A fork puts that propagation behind a manual rebase.
The overlay model keeps one canonical repo. Partners contribute back via PR — registering their slug, adding their overlay folder, suggesting improvements to _common/. Same model, less drift.
Contributing back¶
Partners and EMPN members contribute via PRs on EMPN-eu/empn-core. The contract:
- Universal improvements (better source-tier rules, new editorial patterns, a missing EU institution) go in
_common/. PR review checks the rule is universal. - Identity changes (your logos, your workspaces, your blurb) go in your own overlay folder. PR review makes sure changes don't leak into other installs.
- New skills must be universal by construction. Org-specific behaviour belongs in
shared/scripts/keyed offEMPN_ORG, not in a skill body.
See Contributing for the full conventions.
Reference¶
plugins/empn-core/shared/scripts/setup_org.py— the resolver source.plugins/empn-core/shared/_orgs.yaml— the allow-list of registered organisations.