Conversation
૮ >ﻌ< ა ci reviewran on 55b6a81 — test(wisdom): chat tests run without python-telegram-bot ins debug infoCI timingsCI timings · View report · View jobWall time 5m58s vs 5m20s (+11.9%). 11 job(s) slower, 3 faster,
|
Collaborator
Author
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
force-pushed
the
hermes/hermes-dabab888
branch
from
September 17, 2026 05:20
98a14d0 to
066e55b
Compare
#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
force-pushed
the
hermes/hermes-dabab888
branch
from
September 17, 2026 05:28
066e55b to
55b6a81
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


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-wisdomrepo (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)
chat.pypoller per connected adapter/wisdomchat); mute silenceschat.py/wisdom list/status/updates/candidates/showrender 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'sconfirmuntil an authorized tap (10 min)plugin.tsx+plugin_api.pyupdates.pyMANUAL / AUTO_WITH_NOTICE / REQUIREDapplied 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-passsecurity never auto-appliescandidates.pyon_skill_lifecyclehook → 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,sharestill confirms twiceEverything 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 existingWisdom.confirmcontract (no consent state machine).Changes
plugins/wisdom/package.py— instruction-only package contract: allowed paths (SKILL.md,skill.manifest.json, text underrefs//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 aconfirm(title, detail)callable and refuses without it; installs land inskills/_wisdom/<org>/<slug>/and are picked up by the normal skill index.plugins/wisdom/__init__.py— toolswisdom_browse,wisdom_install,wisdom_share(stripped from the schema until entitled),/wisdomslash command,hermes wisdomCLI. Model-tool consent usestools.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.canonical-hash-vectors.v1.json; instruction-only refusals (scripts/, shebang, exec mode, case collisions, traversal); install writes nothing until confirmed; real bundled discovery +check_fngating.user-guide/features/collective-wisdom.md,hermes wisdomin 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
tests/plugins/+test_tools_config+test_skills_sync_clientdeny→ nothing written;once→ installed, indexed, hashes verifieddenywithdraws draft /oncepublishes (pending_review)hermes wisdom --help,hermes wisdom status(logged out)hermes login" messagegit diff --checkgit merge-tree origin/main HEADtests/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 gatestests/plugins/+ plugin/gateway handler + commands + i18n (153 files)npm run check:lint(tsc + eslint)/plan//install; share echoes the prepared hash)HERMES_HOME, realPluginManager)on_skill_lifecyclefires intocandidate_factsonbump_use/bump_patch;wisdom candidates / not-now / mutework without a Nous loginCallbackQueryHandler(pattern=^wisdom:…)matches our data and ignores the corecp:picker; plugin handlers register before core in group 0Not 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/AsyncAppboundary; 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
Earlier:
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):rmtree(dest)thenrenamepreserved_local_editswhen its hash ≠ ledger; interruption leaves old or new, never neitherskills/_wisdom/.wisdom-*record_installprecedes the swap; scanner sees nothing after a failure"" in detailmatchedField(pattern=sha256)→ 422; whole-line matchowner="owner"client.ownerfrom the tokeninput()on gateway; ledger paths in chatstatus(include_paths=False)thereReview 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, soPluginState, 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=researchwritesprofiles/research/skills/_wisdom/…and that profile's ledger; the default ledger stays empty; unscoped calls unchanged; unknown profile → 404; unauthenticated → 401 still wins. Testtests/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_versionisstring, 1..256— no semver constraint; we sendhermes_cli.__version__(0.21.2), identical to the reverted implementation. All three share bodies (drafts,approve,publish) validate againstWisdomDraftSubmitRequest/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 bytest_generated_manifest_and_share_requests_match_gateway_contract.Not changed: F3 (yolo /
approvals.mode: offbypass 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.