Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .codebuddy-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"name": "skillsearch",
"description": "Per-turn skill retrieval for WorkBuddy.",
"source": "./skillcorpus_plugin/plugin-workbuddy",
"version": "0.3.0",
"version": "0.4.0",
"category": "skill"
}
]
Expand Down
17 changes: 17 additions & 0 deletions docs/releases/v0.4.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## What’s Changed

SkillCorpus v0.4.0 makes the skill library shared across agents: a skill you have in one host is visible to the others, and a skill retrieved from a catalogue is kept instead of thrown away at the end of the turn.

* Added a shared skills library across all six hosts — one shared root at `~/.evermind-skillsearch/`, hosts self-register the directory they actually resolved, retrieved skills are installed rather than cached for a turn, and changes to the shared directory take effect on the next turn with no restart, by @Tian-yi-Sun in https://github.com/EverMind-AI/SkillCorpus/pull/26
* Hardened the repository gates and the release path — stable required-check summaries, full-tree size checks on push, pinned CI dependencies, grouped dependency updates, and a release that validates versions, notes and checksums before publishing, by @cyfyifanchen in https://github.com/EverMind-AI/SkillCorpus/pull/24
* Fixed the Raven plugin schema's `top_k` default, the one host left at 5 when the multi-source release narrowed every other host to 2, by @ypflll in https://github.com/EverMind-AI/SkillCorpus/pull/25

## Upgrade Note

Sharing is on by default. Every host also reads `~/.evermind-skillsearch/skills/` and the directories other hosts registered in `~/.evermind-skillsearch/registry.json`, and skills retrieved from a catalogue are installed there. Set `share_skills` / `shareSkills` to `false` on a host to keep it out, or set an entry's `enabled` to `false` in the registry to stop the others reading that directory — the two switches answer opposite questions, and deleting a registry line does not work because the host re-registers on its next start. `SKILLSEARCH_HOME` moves the shared root.

Raven and Hermes gain `skills_dirs` and `SKILLSEARCH_SKILLS_DIRS`; both previously accepted only a single directory. Raven's `top_k` default is now 2, matching every other host and the engine — a deployment that wants the old value must set it explicitly.

Packages are currently distributed through this GitHub Release. PyPI and npm publication are not yet available.

**Full Changelog**: https://github.com/EverMind-AI/SkillCorpus/compare/v0.3.0...v0.4.0
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "skillcorpus"
version = "0.3.0"
version = "0.4.0"
description = "SkillCorpus — an open pipeline that builds a corpus of agent skills"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
10 changes: 9 additions & 1 deletion scripts/check_project_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,15 @@ def _check_projects() -> list[str]:


def _check_plugin_dirs() -> list[str]:
actual = {path.name for path in PLUGIN_ROOT.iterdir() if path.is_dir()}
# Dot-directories are never packages — `.ruff_cache`, `.pytest_cache` and
# friends appear wherever a tool was invoked from. They are gitignored, but
# this walks the working tree rather than the index, so an ignored
# directory still failed the gate and read as a missing inventory entry.
actual = {
path.name
for path in PLUGIN_ROOT.iterdir()
if path.is_dir() and not (path.name.startswith(".") and path.name != ".github")
}
unexpected = sorted(actual - EXPECTED_PLUGIN_DIRS)
missing = sorted(EXPECTED_PLUGIN_DIRS - actual)
failures = [
Expand Down
2 changes: 1 addition & 1 deletion skillcorpus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ def __getattr__(name):
"SkillRecord", "Category", "CATEGORIES",
"SkillLibrary", "IngestResult", "IngestStatus",
]
__version__ = "0.3.0"
__version__ = "0.4.0"
43 changes: 43 additions & 0 deletions skillcorpus_plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,49 @@

## Unreleased

## 0.4.0 — unreleased

### Added

- **A shared skills library.** Every host now also reads
`~/.evermind-skillsearch/skills/` and registers its own skills directory in
`~/.evermind-skillsearch/registry.json`, so a skill you have in one agent is
visible to the others. Nothing is hardcoded about which agents you run: each
writes the directory it actually resolved at startup, and reads the table
back.
- **Retrieved skills are kept.** A skill downloaded from a catalogue used to be
extracted for one turn and thrown away. It now lands in the shared library
with a `.skillsearch-origin.json` recording where it came from, one directory
per skill rather than per version, and removals append to
`uninstalled.log`.
- **Changes to the shared directory take effect on the next turn**, with no
restart. A host's own directory keeps that host's existing behaviour.
- `skills_dirs` and `SKILLSEARCH_SKILLS_DIRS` on Raven and Hermes, which had
only a single `skills_dir` and no environment override while the engine
supported several roots all along.

### Changed

- An unrecognised `mode` is still narrowed to the default rather than failing
the load, but it is now **logged** with the value that was asked for. On both
OpenClaw generations the host rejects a bad value outright, so this covers
the three hosts with no schema to validate against, and the environment
override on all of them.
- A source that is down is reported through the host's logger on both OpenClaw
packages. The engine always emitted the diagnostic; nothing consumed it, so
an unreachable catalogue and an empty one were indistinguishable.
- A whitespace-only environment variable now counts as unset on the TypeScript
side, matching Python. It previously emptied `skillsDirs`, taking the host's
own skills directory — and with it the shared library — silently.

### Notes

- Two switches, deliberately opposite: `enabled` in the registry is "the others
cannot see me"; `shareSkills` / `share_skills` in a host's own config is "I
cannot see the others".
- `~/.evermind-skillsearch/` is shared. Uninstalling one agent should remove
that agent's line from the registry, not the directory.

## 0.3.0 — 2026-09-02

### Changed — read this before upgrading
Expand Down
41 changes: 41 additions & 0 deletions skillcorpus_plugin/INSTALL.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,42 @@ python -c "import skillsearch, skillsearch_raven; print('import ok')"
plainly during installation. The user can set any endpoint to an empty
string to disable that source, or clear all three for local-only operation.

## The shared skills library

From 0.4.0 every host also reads a directory shared with the user's other
agents, and registers its own skills directory so those agents can read it
back. Nothing needs configuring, but **say it out loud during installation** —
it is a new directory in the user's home and a new place their skills become
visible from:

```text
~/.evermind-skillsearch/
├── registry.json which agent keeps its skills where
├── uninstalled.log what retrieval installed and later removed
└── skills/ what retrieval installed
```

Two switches exist and they are opposites, so establish which one the user
means before touching either:

| The user wants | Set |
| --- | --- |
| other agents not to see this one's skills | `enabled: false` on its line in `registry.json` |
| this agent not to see the others' | `shareSkills` / `share_skills` false in this host's own config |

Deleting a line from `registry.json` does nothing lasting: that agent
re-registers on its next start. That is why the first switch is a flag.

Two consequences worth stating plainly, because they change what the user's
disk holds:

- **Retrieved skills are kept**, not discarded after the turn. Each carries a
`.skillsearch-origin.json` saying where it came from, and removals append to
`uninstalled.log`.
- **Changes to the shared directory take effect next turn**, with no restart.
Changes inside a host's own directory keep that host's existing behaviour,
which for four of the five still means restarting.

## Verification — definition of done

Do all of these; the install is done only when every box is ticked.
Expand Down Expand Up @@ -273,5 +309,10 @@ When the user asks to remove skillsearch:
`pip uninstall skillsearch skillsearch-raven`.
2. Offer to delete the bundle cache (`~/.skillsearch/hub`,
`~/.openclaw/skillsearch-bundles`, or `~/.dsh/skillsearch-bundles`).
**`~/.evermind-skillsearch/` is different — leave it unless this was the
last agent.** It is shared, so deleting it while another agent still has the
plugin takes that agent's library too. Removing this host's line from
`registry.json` is the right narrow cleanup; tell the user what the
directory holds so they can decide about the rest.
3. Restore or delete the `.bak-skillsearch` backups per the user's call.
4. Show the diffs, same rule as installing.
41 changes: 39 additions & 2 deletions skillcorpus_plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,49 @@ Honest accounting, because retrieval runs on your conversation:

- **Local-only setup (after explicitly disabling the three remote endpoints)** — nothing. Scanning, ranking and injection are all in-process.
- **Default installation** — EverMind SkillHub, ClawHub, and skillhub.cn are enabled; the retrieval query is sent to all three services. Set any endpoint field to an empty string to disable that source. With no `model`, no LLM gate runs: source safety checks and the EverMind lexical relevance guard still apply.
- **EverMind SkillHub** — selected skills' bodies and bundles are downloaded from it. Bundles are unzipped with path-traversal rejection, an extension allowlist, and 8 MiB/file, 64 MiB/archive caps, into a cache directory outside every scanned skills dir (`~/.workbuddy-ai/skillsearch-bundles`, `~/.skillsearch/hub`, `~/.openclaw/skillsearch-bundles`, or `~/.dsh/skillsearch-bundles` by default).
- **EverMind SkillHub** — selected skills' bodies and bundles are downloaded from it. Bundles are unzipped with path-traversal rejection, an extension allowlist, and 8 MiB/file, 64 MiB/archive caps, and are **kept** in the shared skills library (`~/.evermind-skillsearch/skills/`) rather than discarded after the turn. Each keeps a `.skillsearch-origin.json` saying what it is and when it arrived, removals are appended to `~/.evermind-skillsearch/uninstalled.log`, and `shareSkills: false` / `share_skills: false` puts a host back on the old throwaway cache.
- **Marketplace body fetches** — up to two candidates per enabled marketplace are downloaded and safely extracted before the optional LLM gate, because those APIs expose the skill body through the bundle. A rejected candidate may therefore remain in the cache, but the plugin never executes it automatically.
- **With `model` set** — the rewriter sees your message (truncated to 2,000 chars); the gate sees your message plus candidate names, descriptions and 300-char body excerpts. Both go to the model *you* configured, through the host's own provider where the host offers one.

Downloaded skills are third-party content that the model is instructed to follow. ClawHub and skillhub.cn entries are not covered by SkillCorpus’s repository-license audit; review their upstream terms before redistribution. The gate can reject skills that assume unavailable tools or environments, but it only exists when a model is configured.

## The shared skills library

By default every host scans one directory of its own, so a skill you have in
one agent is invisible to the other four. From 0.4.0 they also share one:

```text
~/.evermind-skillsearch/
├── registry.json which agent keeps its skills where
├── uninstalled.log what was removed, and when
└── skills/ what retrieval installed, one directory per skill
```

Nothing is hardcoded about *your* agents. Each host writes the skills
directory it actually resolved into `registry.json` when it starts, and reads
the others back — so the set is exactly "the agents that also have this plugin",
and moving your skills directory is picked up on the next start.

`registry.json` is meant to be edited. To stop other agents reading one
directory, set its `enabled` to `false`; deleting the line does not work,
because that agent re-registers on its next start. That is the opposite switch
from `shareSkills` / `share_skills` in a host's own config, which stops *that
host* reading everyone else:

| You want | Set |
| --- | --- |
| others not to see my skills | `enabled: false` on my line in `registry.json` |
| me not to see theirs | `shareSkills: false` in my own host config |

Changes to the shared directory take effect **on the next turn**, with no
restart — drop a skill in by hand and the next question can find it. Changes
inside a host's own directory keep that host's existing behaviour, which for
four of the five still means restarting.

`SKILLSEARCH_HOME` moves the root. Treat it as advanced: a GUI-launched agent
never reads your shell profile, so the two would disagree about where the
library is.

## Make your skills findable

Since retrieval indexes **name and description** (deliberately — the body is where stopword noise lives), the description is your skill's search surface. Write the situations, not just the topic:
Expand All @@ -167,7 +204,7 @@ A skill with no description at all can only be found by its name. (`index_body:

## Uninstall

Reverse of install, nothing hidden: remove the plugin directory / pip packages, delete the config keys you added, and optionally the bundle cache directory listed above. Each plugin README has the exact paths, and the agent playbook has an [uninstall section](INSTALL.agent.md#uninstall) — "remove skillsearch" works too.
Reverse of install, nothing hidden: remove the plugin directory / pip packages, delete the config keys you added, and optionally the bundle cache directory listed above. **`~/.evermind-skillsearch/` is the exception — leave it unless this is your last agent**: it is shared, so deleting it while another agent still has the plugin takes that agent's library with it. The narrow cleanup is removing this host's line from `registry.json`. Each plugin README has the exact paths, and the agent playbook has an [uninstall section](INSTALL.agent.md#uninstall) — "remove skillsearch" works too.

## How it works

Expand Down
28 changes: 26 additions & 2 deletions skillcorpus_plugin/README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,36 @@ EOF

- **显式清空三个远程 endpoint 后的纯本地模式**——什么都不出去。扫描、排序、注入全在进程内。
- **默认安装**——EverMind SkillHub、ClawHub 与 skillhub.cn 默认开启,检索查询会发送给三个服务;将任一 endpoint 设为空字符串可单独关闭。未配置 `model` 时不会运行 LLM gate,但仍执行来源安全检查和 EverMind 关键词相关性过滤。
- **EverMind SkillHub**——选中技能的正文和 bundle 会从它下载。zip 解包有路径穿越拒绝、扩展名白名单、单文件 8 MiB / 整包 64 MiB 上限,缓存目录在所有被扫描技能目录之外(默认 `~/.workbuddy-ai/skillsearch-bundles`、`~/.skillsearch/hub`、`~/.openclaw/skillsearch-bundles` 或 `~/.dsh/skillsearch-bundles`)。
- **EverMind SkillHub**——选中技能的正文和 bundle 会从它下载。zip 解包有路径穿越拒绝、扩展名白名单、单文件 8 MiB / 整包 64 MiB 上限;而且**装下来会留着**,落在共享技能库 `~/.evermind-skillsearch/skills/` 里,不再是用完即弃。每个技能带一份 `.skillsearch-origin.json` 记来源和装入时间,卸载会追加到 `~/.evermind-skillsearch/uninstalled.log`。把宿主配置里的 `shareSkills` / `share_skills` 设为 false,可退回旧的一次性缓存行为。
- **Marketplace 正文获取**——每个启用的 marketplace 最多会有两个候选在可选 LLM gate 之前下载并安全解包,因为这两个 API 通过 bundle 提供技能正文。被 gate 拒绝的候选可能仍留在缓存里,但插件不会自动执行它。
- **配了 `model`**——改写器看到你的消息(截断到 2,000 字符);gate 看到你的消息加候选技能的名字、描述和 300 字符正文摘录。两者都发给**你自己配置的**模型,宿主有 provider 通道的走宿主通道。

下载的技能是第三方内容,模型会被指示遵循它。ClawHub 与 skillhub.cn 条目不在 SkillCorpus 的仓库许可证审计范围内,重新分发前应检查其上游条款。gate 能剔除依赖不可用工具或环境的技能,但只有配置了模型时才真正存在。

## 共享技能库

默认情况下每个宿主只扫自己那一个目录,所以你在一个 agent 里有的技能,另外四个看不见。0.4.0 起它们还共用一个:

```text
~/.evermind-skillsearch/
├── registry.json 哪个 agent 的技能放在哪
├── uninstalled.log 卸载过什么、什么时候
└── skills/ 检索装进来的技能,一个技能一个目录
```

**你的 agent 路径没有一处是写死的。** 每个宿主启动时把自己实际解析出来的技能目录写进 `registry.json`,再把整张表读回去——所以它看到的正好是"同样装了这个插件的那些 agent",而你挪了技能目录,下次启动就自动跟上。

`registry.json` 是给人改的。要让别的 agent 不扫某个目录,把那条的 `enabled` 设成 `false`;**直接删掉那行无效**,那个 agent 下次启动会重新登记。它和宿主自己配置里的 `shareSkills` / `share_skills` 是**反方向**的两个开关:

| 你想要 | 改哪儿 |
| --- | --- |
| 别人别看我的技能 | `registry.json` 里我那条的 `enabled: false` |
| 我不想看别人的技能 | 我这个宿主自己配置里的 `shareSkills` / `share_skills` |

共享目录的变更**下一轮生效,不用重启**——手动拖一个技能进去,下一个问题就能搜到。宿主自己目录里的变更仍按各家原来的规矩,五家里有四家还是要重启。

`SKILLSEARCH_HOME` 能改根目录,但当成高级用法:GUI 启动的 agent 读不到你的 shell profile,两边会对"库在哪"产生分歧。

## 让你的技能可被搜到

检索索引的是**名字和描述**(有意为之——正文是停用词噪声的来源),所以 description 就是技能的搜索面。写触发场景,不要只写主题:
Expand All @@ -144,7 +168,7 @@ description: PDF helper.

## 卸载

安装的逆操作,没有暗桩:删插件目录 / pip 卸载、删你加的配置键、可选删上面列的 bundle 缓存目录。各插件 README 有精确路径,agent 剧本里也有[卸载节](INSTALL.agent.md#uninstall)——对 agent 说"卸载 skillsearch"同样管用。
安装的逆操作,没有暗桩:删插件目录 / pip 卸载、删你加的配置键、可选删上面列的 bundle 缓存目录。**`~/.evermind-skillsearch/` 例外——除非这是最后一个 agent,否则别删**:它是共享的,还有别的 agent 装着插件时删掉它,会把那些 agent 的技能库一起带走;正确的窄清理是把这个宿主那一行从 `registry.json` 里去掉。各插件 README 有精确路径,agent 剧本里也有[卸载节](INSTALL.agent.md#uninstall)——对 agent 说"卸载 skillsearch"同样管用。

## 工作原理

Expand Down
2 changes: 1 addition & 1 deletion skillcorpus_plugin/engine-python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "skillsearch"
version = "0.3.0"
version = "0.4.0"
description = "Skill retrieval for agent hosts — one engine, five host adapters."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
12 changes: 12 additions & 0 deletions skillcorpus_plugin/engine-python/skillsearch/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ class SearchConfig:
builtin_dir: str = ""
"""Read-only skills shipped with the host."""

install_to_shared: bool = True
"""Install retrieved skills into the shared library rather than the cache.

The cache is deliberately outside every scan, so a skill downloaded for
one turn is thrown away and re-downloaded for the next, and no other agent
ever sees it. Installing into the shared directory keeps it: one copy, on
disk, scanned by every host that opted in, with a provenance marker so
fusion knows it is the catalogue's own entry rather than a second skill.

Off restores the pre-0.4 behaviour exactly.
"""

scan_depth: int = 5

index_body: bool = False
Expand Down
Loading
Loading