Skip to content

feat(wisdom): Collective Wisdom returns as a ~2.5k-line bundled plugin with chat, Desktop, update-policy and share-candidate parity (after #108507) - #108678

Open
teknium1 wants to merge 9 commits into
mainfrom
hermes/hermes-dabab888
Open

teknium1 wants to merge 9 commits into
mainfrom
hermes/hermes-dabab888

Conversation

@teknium1

@teknium1 teknium1 commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator

Collective Wisdom is back as a bundled plugin at ~2.5k lines (Python + Desktop) instead of the 80,892 that #94266 put in core and #108507 removed — now at feature parity with the standalone hermes-collective-wisdom repo (31k lines) on every surface it covered: in-chat notifications, Telegram and Slack management cards, Desktop, update policy + conflicts from the gateway, and share-candidate qualification.

Follow-up to #108507 (revert of #94266). Same Gateway, same wire contract, same consent guarantees; the surface is one plugins/wisdom/ directory with zero core edits.

Parity (added in the Sep 16 push)

Feature Where How it is built
In-chat notifications chat.py poller per connected adapter delivers "team published X", "policy applied/needs you", "worth sharing" cards once each to the platform home channel (or the last /wisdom chat); mute silences
Telegram + Slack management chat.py /wisdom list/status/updates/candidates/show render inline-keyboard / Block Kit cards; buttons carry an opaque 12-hex token (no ids/hashes on the wire, 21 bytes); taps authorized via the adapter's own user check; every mutation posts an Approve / Deny card that blocks the service's confirm until an authorized tap (10 min)
Desktop plugin.tsx + plugin_api.py "Updates needing your decision" (policy badge, Replace / Keep mine), "Worth sharing" (Share… → prepared-package dialog with file list + hash → publish only on pass/pass; Not now); status-bar count
Update policy + conflicts updates.py Gateway MANUAL / AUTO_WITH_NOTICE / REQUIRED applied on a 10-min sweep (session start, chat poller, Desktop overview); a tree whose bytes drifted from the installed hash is a local edit and is never overwritten silently: REQUIRED lands with the edited copy parked, AUTO_WITH_NOTICE becomes a conflict (Replace / Keep mine = update --keep), non-pass security never auto-applies
Qualification checks candidates.py on_skill_lifecycle hook → per-skill day sets in plugin state; 7 consecutive business days of use, or 3+ refinements then a stable, still-used week; bundled/hub/Wisdom/shared skills excluded; 3 per ISO week; not-now = 30-day cooldown; a candidate is only a suggestion, share still confirms twice

Everything sits on host primitives the standalone repo re-implemented: PluginState (no SQLite store with 37 tables), register_telegram_handler / register_slack_action_handler / register_platform_handler (no outbox, no receipts), spawn_task (no delivery leases), on_skill_lifecycle (no snapshot tables), the existing Wisdom.confirm contract (no consent state machine).

Changes

  • plugins/wisdom/package.py — instruction-only package contract: allowed paths (SKILL.md, skill.manifest.json, text under refs//assets/), size caps, canonical content-manifest and author-description hashing, manifest schema v1, spec inference from frontmatter.
  • plugins/wisdom/client.py — /v1/sync/wisdom/ over the existing Nous sync identity. Every blob and the assembled package are hash-verified before anything touches disk. Entitlement = wisdom:* scopes on a fresh local token (advisory; the Gateway authorizes).
  • plugins/wisdom/service.py — browse / show / status / install / update / uninstall / share. Every mutation takes a confirm(title, detail) callable and refuses without it; installs land in skills/_wisdom/<org>/<slug>/ and are picked up by the normal skill index.
  • plugins/wisdom/__init__.py — tools wisdom_browse, wisdom_install, wisdom_share (stripped from the schema until entitled), /wisdom slash command, hermes wisdom CLI. Model-tool consent uses tools.approval.request_tool_approval, the same gate as dangerous shell commands: once/session/always/deny in the CLI, approval button on gateway platforms, fail-closed when unattended.
  • Tests: byte-exact conformance against the Gateway's published canonical-hash-vectors.v1.json; instruction-only refusals (scripts/, shebang, exec mode, case collisions, traversal); install writes nothing until confirmed; real bundled discovery + check_fn gating.
  • Docs: user-guide/features/collective-wisdom.md, hermes wisdom in the CLI reference.

Still not carried over (infrastructure, not product): the private SQLite store, delivery/operation outboxes and receipts, the setup-command execution lifecycle (installing a skill never runs its setup commands), the 13k-line vendored OpenAPI document, the demo stack.

Validation

Check Result
Hash vectors (content, manifest, description, unicode ordering) byte-exact match
tests/plugins/ + test_tools_config + test_skills_sync_client 1,936 passed, 0 failed
E2E, real discovery + registry dispatch, Gateway mocked at HTTP no human → blocked, nothing written; human deny → nothing written; once → installed, indexed, hashes verified
E2E share package prompt → upload → Gateway recomputes commit/tree/blob hashes and matches → review prompt → deny withdraws draft / once publishes (pending_review)
hermes wisdom --help, hermes wisdom status (logged out) argparse tree registered; clean "run hermes login" message
Tool schema 0 wisdom tools without entitlement, 3 with
ruff, windows-footguns, compat-pointers, git diff --check clean
git merge-tree origin/main HEAD clean
Parity tests tests/plugins/test_wisdom_parity.py (12): policy × edits matrix (6), security gate, qualification/quota/not-now, Telegram card flow (typed /wisdom → coroutine → card; stranger tap refused; consent card shows version + hash + verdict; approve installs, real files; stale token expires), proactive delivery once-per-item + mute, Slack blocks + stranger refusal, Desktop keep + share verdict gates 12 passed
tests/plugins/ + plugin/gateway handler + commands + i18n (153 files) 2,274 passed, 1 fixed (rediscovery test asserted an empty telegram bucket; bundled plugins now legitimately populate it)
Desktop npm run check:lint (tsc + eslint) 0 errors; vitest wisdom plugin 3/3 (keep pins exact version without /plan//install; share echoes the prepared hash)
Real discovery E2E (temp HERMES_HOME, real PluginManager) telegram + slack factories registered, slack action handler registered, on_skill_lifecycle fires into candidate_facts on bump_use/bump_patch; wisdom candidates / not-now / mute work without a Nous login
PTB handler scope CallbackQueryHandler(pattern=^wisdom:…) matches our data and ignores the core cp: picker; plugin handlers register before core in group 0

Not exercised live: a real Gateway round-trip (needs a team with the Wisdom flag) and a real Telegram/Slack bot (the SDK objects are faked at the Application/AsyncApp boundary; the calls used — bot.send_message, edit_message_text, client.chat_postMessage, chat_update, chat_postEphemeral — are the same ones the core adapters make). The HTTP layer is 1:1 with the pinned OpenAPI in #94266 and the server-side hash recomputation is reproduced in the E2E.

Infographic

Collective Wisdom: parity, lean

Earlier: 80K lines to 1 plugin

Review follow-up (79dbc9e)

Six mechanical defects from the Sep 12 review closed in one commit; verified by two new red-on-base invariant tests plus a real-I/O E2E against a temp HERMES_HOME (skill scanner, FastAPI TestClient, plugin state):

Finding Before After
F1 overwritten edits / no rollback rmtree(dest) then rename old tree moved aside first, kept as preserved_local_edits when its hash ≠ ledger; interruption leaves old or new, never neither
F2 failed install discoverable staging in skills/_wisdom/.wisdom-* staging under plugin data dir; record_install precedes the swap; scanner sees nothing after a failure
F4 Desktop empty hash "" in detail matched Field(pattern=sha256) → 422; whole-line match
F5 wrong author owner="owner" client.owner from the token
F8 slash confirm / paths input() on gateway; ledger paths in chat approval gate on gateway surfaces; status(include_paths=False) there
F9 CLI exit code always 0 1 on error

Review follow-up 2 (98a14d0) — F7 fixed at the host, F6 refuted

F7 hermes_cli/web_server_dashboard.py::_PluginProfileScopeMiddleware: every /api/plugins/<name>/* request now runs under the ?profile= home override the Desktop already sends, so PluginState, the install ledger and Nous credentials resolve in the selected profile. Fixes kanban's plugin API the same way. Live E2E: two profiles, POST /install?profile=research writes profiles/research/skills/_wisdom/… and that profile's ledger; the default ledger stays empty; unscoped calls unchanged; unknown profile → 404; unauthenticated → 401 still wins. Test tests/hermes_cli/test_web_server_plugin_profile_scope.py (red with the middleware inert).

F6 ("incorrect version information"): checked against the Gateway OpenAPI pinned in the pre-revert tree. WisdomSystemSpec.hermes.minimum_version is string, 1..256 — no semver constraint; we send hermes_cli.__version__ (0.21.2), identical to the reverted implementation. All three share bodies (drafts, approve, publish) validate against WisdomDraftSubmitRequest / ApproveRequest / PublishRequest (additionalProperties: false), and the generated manifest validates against the spec. The only real publish blocker was the author owner (F5, fixed in 79dbc9e). Pinned by test_generated_manifest_and_share_requests_match_gateway_contract.

Not changed: F3 (yolo / approvals.mode: off bypass the plugin gate — host-wide approval contract; opting one plugin out needs a new primitive). Deferred workflow scope (qualification, notice cards, compat/setup lifecycle, update policy) is unchanged.

@github-actions

github-actions Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

૮ >ﻌ< ა ci review

ran on 55b6a81 — test(wisdom): chat tests run without python-telegram-bot ins

debug info

CI timings

CI timings · View report · View job

Wall time 5m58s vs 5m20s (+11.9%). 11 job(s) slower, 3 faster,

  • Docs Site / docs-site-checks: -41.0s
  • Python tests / Run tests: +35.0s
  • OS-specific tests / Windows-only tests: +32.0s
  • JS & TS checks / JS & TS checks: +14.0s
  • Python lints / Windows footguns (blocking): -8.0s

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins tool/skills Skills system (list, view, manage) labels Sep 12, 2026
@teknium1

teknium1 commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

Round 2 (38edf7ac): notices, Desktop page, mute — +515 net LOC

Item Where LOC
① Proactive "your team published X" notices plugins/wisdom/notices.py + prompt-section registration ~110
② Desktop Team Skills page + status-bar count apps/desktop/src/plugins/wisdom/plugin.tsx + plugins/wisdom/dashboard/plugin_api.py (5 routes) ~260
③ Telegram/Slack nothing new: /wisdom + approval-gate button already work there 0
tests + docs vitest (plan-hash echo), 2 pytest (feed diff/freeze; router hash-binding), docs ~145

Design notes: notices are polled at most once / 10 min / profile and frozen into each new session's system prompt (register_system_prompt_section), so they never mutate a live conversation or break the prompt cache. Desktop /install echoes the content_hash from /plan; a republish between plan and click fails closed (409, tested).

Validation: tests/plugins + test_commands + web-server + system-prompt suites 2,349 passed; desktop tsc clean, eslint clean, vitest green. E2E through real discovery: no section when not entitled → section with the notice when entitled → /wisdom mute 2 → section gone; dashboard discovers the plugin (bundled, hidden tab) and mounts all 5 routes.

Screenshots (real component, Vite + Playwright, fixture REST):

Team Skills page Install plan dialog
page dialog

teknium1 added a commit that referenced this pull request Sep 12, 2026
…nd Desktop install to a real hash

Review of #108678 found seven mechanical defects; this commit closes the six that need no design
call (auto-approve bypass and Desktop profile routing are host-wide and stay as-is):

- install/update parked the previous tree with shutil.rmtree before the new one landed; an
  interruption left neither, and a user's edits vanished. Now the old tree is moved aside under
  plugin state, the new one moved in, and the old copy is kept (reported as preserved_local_edits)
  whenever its content hash differs from what the ledger says was installed.
- staging lived in skills/_wisdom/.wisdom-*, which the SKILL.md scanner rglobs; a failed install
  left a discoverable skill. Staging now lives under the plugin's data dir; record_install runs
  before the local swap so the only step after the Gateway accepts is a rename.
- the Desktop /install route accepted content_hash="" (substring match against the plan text);
  the field is now schema-bound to a full sha256 address and matched as a whole line.
- share() built the draft commit with the literal author owner="owner"; the Gateway's attribution
  guard (author_mismatch 422) rejects that. The client now exposes the token's owner.
- /wisdom bound every mutating verb to the terminal input() prompt even inside a gateway chat;
  on a gateway surface it now goes through the shared approval gate, and status omits local paths.
- hermes wisdom returned 0 on error strings; failures now exit 1.
teknium1 added a commit that referenced this pull request Sep 17, 2026
…nd Desktop install to a real hash

Review of #108678 found seven mechanical defects; this commit closes the six that need no design
call (auto-approve bypass and Desktop profile routing are host-wide and stay as-is):

- install/update parked the previous tree with shutil.rmtree before the new one landed; an
  interruption left neither, and a user's edits vanished. Now the old tree is moved aside under
  plugin state, the new one moved in, and the old copy is kept (reported as preserved_local_edits)
  whenever its content hash differs from what the ledger says was installed.
- staging lived in skills/_wisdom/.wisdom-*, which the SKILL.md scanner rglobs; a failed install
  left a discoverable skill. Staging now lives under the plugin's data dir; record_install runs
  before the local swap so the only step after the Gateway accepts is a rename.
- the Desktop /install route accepted content_hash="" (substring match against the plan text);
  the field is now schema-bound to a full sha256 address and matched as a whole line.
- share() built the draft commit with the literal author owner="owner"; the Gateway's attribution
  guard (author_mismatch 422) rejects that. The client now exposes the token's owner.
- /wisdom bound every mutating verb to the terminal input() prompt even inside a gateway chat;
  on a gateway surface it now goes through the shared approval gate, and status omits local paths.
- hermes wisdom returned 0 on error strings; failures now exit 1.
@teknium1
teknium1 force-pushed the hermes/hermes-dabab888 branch from 98a14d0 to 066e55b Compare September 17, 2026 05:20
@teknium1 teknium1 changed the title feat(wisdom): Collective Wisdom returns as a ~1k-line bundled plugin (after #108507) feat(wisdom): Collective Wisdom returns as a ~2.5k-line bundled plugin with chat, Desktop, update-policy and share-candidate parity (after #108507) Sep 17, 2026
#94266 shipped Collective Wisdom as an 80k-line in-tree feature spanning a
core package, three model tools, CLI/gateway/TUI/dashboard/desktop surfaces
and Telegram/Slack adapters; #108507 deleted it. This brings the capability
back at the footprint it should have had: one bundled plugin under
plugins/wisdom/ with zero core edits.

- package.py: the Gateway's instruction-only contract (allowed paths, size
  caps, canonical content-manifest + author-description hashing, manifest
  schema v1). Byte-exact against the Gateway's published hash vectors.
- client.py: /v1/sync/wisdom/ over the shared Nous sync identity; every
  downloaded blob and the whole package are hash-verified before use.
- service.py: browse / show / status / install / update / uninstall /
  share, each mutation behind a caller-supplied confirm() so nothing is
  applied without a human seeing the exact version, hashes and Gateway
  verdicts. Installs live under skills/_wisdom/<org>/<slug>/ and are
  indexed like any other skill.
- __init__.py: tools wisdom_browse / wisdom_install / wisdom_share (visible
  only when the Nous token carries wisdom:* scopes), the /wisdom slash
  command and `hermes wisdom` CLI. Model-tool consent rides the same human
  approval gate as dangerous shell commands (fail-closed when unattended).

Dropped on purpose: proactive advice queues, delivery leases, weekly agent
review, Telegram/Slack card adapters, Desktop/dashboard panels, the 13k-line
vendored OpenAPI document and the demo stack. Those are product surface for a
later plugin iteration, not core.
The collector lists plugin slash commands before skills. The wisdom plugin is
the first bundled kind=backend plugin that registers a slash command, so
entries[0] is now a plugin row; assert on the row whose cmd_key matches.
Round two of the plugin rebuild, everything still inside plugins/wisdom/ plus one
bundled Desktop renderer plugin. Net +515 lines.

- notices.py: one bounded feed poll per profile per 10 min, diffed against the
  install ledger, rendered as a system-prompt section frozen into each NEW
  session (cache-safe by construction). Retracted/taken-down events drop the
  notice; install/uninstall clear it. `hermes wisdom mute [hours]` / `/wisdom
  mute` silence it. Nothing is downloaded or installed by a notice.
- service.plan(): the human-readable install plan (exact version, content hash,
  Gateway verdict, target path) extracted so every surface shows the same facts.
- dashboard/plugin_api.py: /overview, /plan, /install, /uninstall,
  /notices/dismiss under /api/plugins/wisdom/. /install echoes the planned
  content_hash and fails closed (409) if the package changed since the plan.
  Manifest is tab-hidden: the web dashboard gets no page, only the router.
- apps/desktop/src/plugins/wisdom: "Team Skills" sidebar page (catalog,
  installed versions, pending updates, Install/Update/Remove with a
  ConfirmDialog showing the plan) and a status-bar count of pending updates.
  Pure SDK consumer over ctx.rest; empty state when not entitled.

Telegram/Slack get nothing new on purpose: /wisdom and the approval-gate button
already work there; native rich cards were 2k lines to prettify a button.
…nd Desktop install to a real hash

Review of #108678 found seven mechanical defects; this commit closes the six that need no design
call (auto-approve bypass and Desktop profile routing are host-wide and stay as-is):

- install/update parked the previous tree with shutil.rmtree before the new one landed; an
  interruption left neither, and a user's edits vanished. Now the old tree is moved aside under
  plugin state, the new one moved in, and the old copy is kept (reported as preserved_local_edits)
  whenever its content hash differs from what the ledger says was installed.
- staging lived in skills/_wisdom/.wisdom-*, which the SKILL.md scanner rglobs; a failed install
  left a discoverable skill. Staging now lives under the plugin's data dir; record_install runs
  before the local swap so the only step after the Gateway accepts is a rename.
- the Desktop /install route accepted content_hash="" (substring match against the plan text);
  the field is now schema-bound to a full sha256 address and matched as a whole line.
- share() built the draft commit with the literal author owner="owner"; the Gateway's attribution
  guard (author_mismatch 422) rejects that. The client now exposes the token's owner.
- /wisdom bound every mutating verb to the terminal input() prompt even inside a gateway chat;
  on a gateway surface it now goes through the shared approval gate, and status omits local paths.
- hermes wisdom returned 0 on error strings; failures now exit 1.
The Desktop appends ?profile=<name> to every REST call (profileScoped()). Core routers read it
per handler; plugin routers mounted under /api/plugins/<name>/ never did, so a plugin's state,
ledger and credentials silently came from the serve process's own profile (Wisdom and kanban
alike). A middleware now holds the context-local HERMES_HOME override for the whole plugin
namespace, set in the request's own context so the handler's threadpool copy inherits it (a
FastAPI dependency enters and exits in different contexts, which the ContextVar token refuses).
Config-only scope: no process-global module retargeting, await-safe. Unknown profile -> 404;
auth still runs first (middleware registered innermost).

Also pins the Wisdom share wire contract: generated manifest validates against the strict schema
mirror, commit author is the token owner, and draft/approve/publish bodies carry exactly the
Gateway's fields as sha256 addresses.
…lack cards and proactive notices

Parity with the standalone plugin's chat and lifecycle features, on the host's own primitives
instead of a private persistence layer:

- updates.py: the Gateway's per-installation policy (MANUAL / AUTO_WITH_NOTICE / REQUIRED) is
  applied on a rate-limited sweep; a managed tree whose bytes drifted from the installed hash is a
  local edit and is never overwritten silently — REQUIRED lands with the edited copy parked,
  AUTO_WITH_NOTICE becomes a conflict the user resolves (replace / keep). Non-passing security
  verdicts never auto-apply. `wisdom update --keep`, `wisdom updates`.
- candidates.py: deterministic share-candidate qualification from the on_skill_lifecycle hook
  (7 consecutive business days of use, or 3+ refinements then a stable, still-used week), weekly
  quota, "not now" cooldown, shared skills excluded. `wisdom candidates`, `wisdom not-now`.
- chat.py: Telegram inline-keyboard and Slack Block Kit cards for /wisdom (list, status, updates,
  candidates, show) with opaque button tokens, actor authorization through the adapter's own
  check, and native Approve/Deny consent cards that block the service's confirm until an
  authorized tap. A supervised poller per connected platform delivers team notices, policy
  results, conflicts and candidates once each to the home channel.
- notices.prompt_section now freezes all three blocks into a new session's prompt.
- service.py: `time` import (vetting wait), plan shows the update mode, share records what was
  shared so it is never suggested again.

Test fixture: a live named profile now needs an identity marker (main), so the plugin profile
scope test writes a config.yaml.
…er chat bindings; docs

- Desktop Team Skills page: "Updates needing your decision" (policy badge, Replace keeps a copy of
  edits / Keep mine), "Worth sharing with your team" (Share… opens the prepared-package dialog,
  Not now snoozes); status-bar count includes both. REST: /update/keep, /candidates/not-now,
  /share/prepare (local packaging, no upload), /share (hash-echo + publish only on pass/pass).
- chat.py binds cards and pollers to the connected adapter (`Live`), keyed by platform + profile
  home, so a multiplexed gateway never answers through another profile's bot.
- Docs: Telegram/Slack cards, update policy table, share candidates, CLI reference rows.
…is gone, not an empty bucket

The bundled Wisdom plugin now registers a Telegram handler factory at load, so a real rediscovery
legitimately repopulates the bucket; the invariant is that the test plugin's lease was released.
CI has no PTB; the plugin only imports it when a Telegram adapter is connected. The keyboard
builder is a seam the tests stand in for, and the handler class is stubbed in sys.modules.
@teknium1
teknium1 force-pushed the hermes/hermes-dabab888 branch from 066e55b to 55b6a81 Compare September 17, 2026 05:28

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have tool/skills Skills system (list, view, manage) type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants