Skip to main content

profile-pane

❖ Community★ 0
Open in Hermes Desktop
hermes plugins install profile-pane

README

From the reviewed commit 2b480df ↗; it updates when the author re-pins.

profile-pane

Cross-agent configuration as a desktop pane. Stage skills onto Hermes agents and keep SOUL / memory / user / profile layers matched to a group's anchor — on demand, or on a schedule.

version license platform


What it is

profile-pane is a native Hermes desktop pane at ~/.hermes/plugins/profile-pane/. You do not hand-edit SOUL.md or MEMORY.md across nine profiles by hand; you pick which member is canonical and the pane copies FROM it INTO the others.

The anchor model (the one idea)

The pane never asks you to author a SOUL. It asks which member is canonical (policy.anchor), and every layer copies from that anchor into the others. A design that takes SOUL text as input is wrong — it puts authoring on you and has no source of truth.

SOUL scope what lands in each member
whole the anchor's file, byte for byte — members become identical
blocks the anchor's text spliced under ownership markers; each member's own text kept
section only the ## sections you tick, spliced the same way

blocks / section splice into <!-- profile-pane:shared:begin --> … end. A re-apply replaces that region — it never grows a second copy — which is what makes a schedule safe to leave running.


Status

Alpha (0.1.0). Honest limits, stated up front:

  • Every end-to-end run has been sandboxed — it has not been applied to a live profile.
  • The auto switch ships OFF on every group; turning it on is a per-group decision.
  • The scheduled job is silent by design — it reports only when it actually moved something.

What it syncs

layer source push append
Skills the union of all profiles copy + manifest (never symlink) n/a
SOUL SOUL.md (whole-file, no CAS) whole / blocks / section scope n/a
Memory memories/MEMORY.md (§-block file) member identical to anchor anchor entries present, member keeps extras
User memories/USER.md (§-block file) member identical to anchor anchor entries present, member keeps extras
Profile profile.yaml — the description scalar only description = anchor's n/a — one scalar
Secrets key list (advisory) reported, never forced n/a

ui_meta in profile.yaml is never touched — the gateway CAS-guards it per key, and this plugin writes only the authored description.

append is how a member keeps its differences. It is deterministic, so it re-runs to a no-op like everything else.


Two ways it runs

  • Apply (button) — copies staged skills and pushes the enabled layers, once.
  • Auto (per group) — tools/sync_once.py re-runs the group via the profile-pane auto-sync cron job (every 15m, no_agent, silent when nothing moved).

The manifest is the desired state. An Apply records the skills it copied and their source; the reconciler re-copies only those, only when the source has moved. A first run on an untouched profile is a no-op.


Guardrails

  • Fixed point. Writes are idempotent — an unchanged file is not rewritten (no snapshot litter, no mtime churn).
  • Staging Review before Apply all - natural language description list of changes
  • Snapshot before every write. SOUL and memory files have no CAS and are not atomic; the snapshot is the reverse.
  • Secret scan still blocks. A refused copy is reported, never forced. Auto-sync is not a bypass around the gate manual Apply respects.
  • Manifest-bounded. Nothing syncs that the pane did not already apply once.
  • Dry run is the default. dry_run must be false to write.
  • No guessed source. An absent anchor is refused, never defaulted to "the first profile."
  • The anchor is never written to itself.

Install

git clone https://github.com/taxbax/profile-pane.git ~/.hermes/plugins/profile-pane

Then enable it: Settings → Plugins → profile-pane. The Python half mounts once at gateway start; the JS half hot-reloads from desktop-plugins/.


Operating it

Open the pane, then:

  • ACTIVITY bar (top) lights accent when work is staged — it never opens itself. Staged skills list, then Preview (writes nothing) and Apply (writes, records its own reverse).
  • SYNC POLICY — group tabs with members, the layer toggles, and the detail for whichever layer has any.
  • SKILLS | PROFILES flip — the library and the member list.

The library is the machine, not a profile: /all-skills returns the union of every profile's deployed skills, marked against the focused profile — bold + ✓ means already there, plain means available. Each skill resolves to whichever profile actually has it.

Copies are paired, never a cross-product — a request carries pairs: [{profile, skill, op}], and /plan reads the same pairs, so a preview cannot report a different count than the apply it guards.

The try-it flow is Apply → Undo last (ACTIVITY ▸ Commits). Undo names what it would reverse. A Trial applies for real, then reverts — the only way to watch a subject behave under a change without keeping it.

Layout:

ACTIVITY          staged N          (the bar LIGHTS UP; it never opens itself)
  [Staged (N)] [Commits (N)]
    Membership (n) · Policy (n) · staged skill rows
    [Preview] [Apply]

SYNC POLICY       (folds to one summary line; GROUP TABS head the panel)
  Group   [operators] [+ group]   rename · delete
  Members [★sys ×] [trad ×] + add
  1 · WHICH LAYERS MOVE   Auto · Skills · Memory · User · Profile · Secrets
  2 · DETAIL FOR THE ROWS THAT HAVE ANY   the Secrets key list, the Soul sections

SKILLS | PROFILES  (flip ⇅ and refresh ⟳ live in the ACTIVITY bar)

The layout encodes "decide what moves, then tune how": every layer toggle is in block 1; every per-layer detail is in block 2, under the row it belongs to. Apply is enabled only when something is actually pending.


Architecture

The Python half is a thin FastAPI router; every rule lives in panecore. The desktop half is a single JS file. ~5,800 lines across the tree, 33 routes, 120 titled controls.

profile-pane/
├── dashboard/
│   ├── plugin_api.py        # thin FastAPI router (33 routes)
│   └── panecore/
│       ├── apply.py         # the only module that writes copies — snapshot, secret scan, manifest
│       ├── soul.py          # anchor render + idempotent splice
│       ├── memory.py        # §-blocks + the profile description
│       ├── sync.py          # the reconciler (manifest = desired state)
│       ├── trial.py         # apply-for-real-then-revert
│       ├── groups.py        # the group / member model
│       ├── secrets.py       # advisory key-list surface
│       ├── models.py        # request / response shapes
│       └── paths.py         # profile + skill path resolution
├── desktop/
│   └── plugin.js            # the pane
├── tools/
│   ├── audit.mjs            # undefined-identifier + invariant + glyph audit
│   └── sync_once.py         # standalone reconcile entrypoint (the cron job)
├── tests/                   # pure-logic suite + the router half
├── skill/SKILL.md           # the operating skill, bundled with the plugin
├── DELIVERY.md              # full delivery notes, wiring, and honest limits
├── plugin.yaml
├── LICENSE
└── README.md

Development

cd ~/.hermes/plugins/profile-pane
node --check desktop/plugin.js                        # syntax
node tools/audit.mjs desktop/plugin.js                # undefined idents + invariants + glyph colour
python3 -m pytest tests/ -q                           # pure-logic suite (system python)
~/.hermes/hermes-agent/venv/bin/python tests/test_crud.py   # the router half (needs FastAPI)

Read the skip. test_crud.py needs FastAPI, which lives only in the Hermes venv — under a plain python3 -m pytest the entire module skips, and that one skipped line is 54 route tests. Both commands are listed so a third of the suite is not silently dropped. The venv has FastAPI but no pytest, so test_crud.py carries a small pytest-free runner and is invoked directly.

Run the two commands and trust the printed numbers — the test count has gone stale before.

The Python half mounts once at gateway start (new routes 404 until the desktop app is quit and reopened). The JS half hot-reloads from desktop-plugins/ — after editing desktop/plugin.js, mirror it over:

cp desktop/plugin.js ~/.hermes/desktop-plugins/profile-pane/plugin.js

License

MIT © 2026 taxbax

← Back to the catalog · catalog built Oct 14, 2026