From 582a8ba2888cb5ed01d245a114ff3d5b6a06e10a Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Tue, 14 Jul 2026 04:32:40 +0800 Subject: [PATCH] refactor: drop unconsumed skill provider events --- docs/cordis-catalog/events.md | 22 ------------------- docs/cordis-catalog/services.md | 2 +- docs/event-producer-consumer.md | 2 -- docs/rfc/INDEX.md | 2 +- .../feature/2026-07-05-skill-system.md | 2 +- ...2-drop-unconsumed-skill-provider-events.md | 19 ++++++---------- .../cordis/tool-cordis/src/api-catalog.ts | 12 ---------- packages/skill/skill/src/index.ts | 22 +------------------ 8 files changed, 11 insertions(+), 72 deletions(-) rename docs/rfc/{proposed => implemented}/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md (52%) diff --git a/docs/cordis-catalog/events.md b/docs/cordis-catalog/events.md index acbd39b8fa..f5ff9242b1 100644 --- a/docs/cordis-catalog/events.md +++ b/docs/cordis-catalog/events.md @@ -285,28 +285,6 @@ Awaited durability checkpoint. The agent loop awaits `ctx.sessions.flush(session Source: [`packages/core/session/src/index.ts:101`](../../packages/core/session/src/index.ts) -## `skill/*` - -### `skill/provider-added` — emit - -A skill provider became resolvable in the `ctx.skills` registry. Consumers can observe this instead of depending on Cordis plugin load order, which is concurrent for sibling plugins. - -```ts cordis-catalog -'skill/provider-added'(provider: SkillProvider): void -``` - -Source: [`packages/skill/skill/src/index.ts:131`](../../packages/skill/skill/src/index.ts) - -### `skill/provider-removed` — emit - -A skill provider left the registry because its plugin fiber was disposed. - -```ts cordis-catalog -'skill/provider-removed'(name: string): void -``` - -Source: [`packages/skill/skill/src/index.ts:137`](../../packages/skill/skill/src/index.ts) - ## `subagent/*` ### `subagent/end` — emit diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md index 0aea3f1589..e417d20fa5 100644 --- a/docs/cordis-catalog/services.md +++ b/docs/cordis-catalog/services.md @@ -231,7 +231,7 @@ async list(options: SkillLookupOptions = {}): Promise async get(name: string, options: SkillLookupOptions = {}): Promise ``` -Source: [`packages/skill/skill/src/index.ts:158`](../../packages/skill/skill/src/index.ts) +Source: [`packages/skill/skill/src/index.ts:141`](../../packages/skill/skill/src/index.ts) ## `ctx.subagents` — `SubagentService` diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 5b0a82673e..dea714031e 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -29,8 +29,6 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `session/disposed` | `emit` | [`packages/core/session/src/index.ts:64`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | - | | `session/event` | `emit` | [`packages/core/session/src/index.ts:83`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/ui/acp), [`invariants`](../packages/support/invariants), [`jsonrpc`](../packages/ui/jsonrpc), [`session-persistence`](../packages/session-persistence/session-persistence), [`stdio-agent`](../packages/ui/stdio-agent) | | `session/flush` | `parallel` | [`packages/core/session/src/index.ts:101`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence) | -| `skill/provider-added` | `emit` | [`packages/skill/skill/src/index.ts:131`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`emit`) | - | -| `skill/provider-removed` | `emit` | [`packages/skill/skill/src/index.ts:137`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`emit`) | - | | `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:90`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`jsonrpc`](../packages/ui/jsonrpc) | | `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:66`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) | | `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:72`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`tool-subagent`](../packages/subagent/tool-subagent) | diff --git a/docs/rfc/INDEX.md b/docs/rfc/INDEX.md index b338edfc52..ae82f8e695 100644 --- a/docs/rfc/INDEX.md +++ b/docs/rfc/INDEX.md @@ -19,7 +19,6 @@ Generated by `pnpm run gen-rfc-index` from the RFC tree — never edit by hand; |---|---| | [Unify the agent id and the session id](proposed/simplification/2026-06-20-unify-agent-and-session-id.md) | 2026-06-20 | | [Prune dead public and result surface](proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md) | 2026-07-04 | -| [Drop unconsumed skill provider events](proposed/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md) | 2026-07-12 | | [Prune unused web seam fields](proposed/simplification/2026-07-12-prune-unused-web-seam-fields.md) | 2026-07-12 | | [Simplify session-log representation](proposed/simplification/2026-07-12-simplify-session-log-representation.md) | 2026-07-12 | @@ -100,6 +99,7 @@ Generated by `pnpm run gen-rfc-index` from the RFC tree — never edit by hand; | [Share the app bins' boot glue instead of maintaining twin copies](implemented/simplification/2026-07-04-share-app-bin-boot-glue.md) | 2026-07-04 | | [Tighten the hook-protocol contract — dialect, discarded fields, double defaults, and lib-owned `hook/result` semantics](implemented/simplification/2026-07-04-tighten-hook-protocol-contract.md) | 2026-07-04 | | [Trim unreachable ACP bridge surface — the branding knobs and the kind-sniffing fallback](implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md) | 2026-07-04 | +| [Drop unconsumed skill provider events](implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md) | 2026-07-12 | ### Architecture diff --git a/docs/rfc/implemented/feature/2026-07-05-skill-system.md b/docs/rfc/implemented/feature/2026-07-05-skill-system.md index b3007529cd..c29140a854 100644 --- a/docs/rfc/implemented/feature/2026-07-05-skill-system.md +++ b/docs/rfc/implemented/feature/2026-07-05-skill-system.md @@ -12,7 +12,7 @@ DeepSeek Harness uses the same primitive so project-specific review, plugin-auth `@deepseek-ai/dsh-skill` is the pure provider registry (`ctx.skills`), `@deepseek-ai/dsh-skill-local` is the shipped local filesystem provider, and `@deepseek-ai/dsh-tool-skill` owns the session-prefix catalog and model-facing loader tool. `dsh-agent-core` loads the registry, local provider, and consumer by default so stdio and ACP apps get the same behavior while embedded or remote providers contribute skills without changing the registry or consumer. Its `skills` config forwards `registry`, `local`, and `tool` branches to those owners. -Provider plugins register synchronously during `apply()`. Provider catalogs return ranked candidates from awaited `list()` calls, where remote providers perform initialization, authentication, and discovery while honoring the lookup abort signal. The registry validates each candidate, resolves same-name skills first-wins by rank, provider registration order, and provider-local order, then sorts summaries by skill name for deterministic consumers. It caches only completed catalog snapshots and retries when a provider/runtime revision changes during discovery, so an unload cannot freeze a stale, unresolvable skill into a session prefix. Runtime `ctx.skills.register(...)` remains a convenience for embedded in-process skills and uses project-over-user priority; `runtime` is reserved as the registry-owned provider name. +Provider plugins register synchronously during `apply()`. Provider membership is direct effect-owned state: registration and disposal invalidate completed catalogs synchronously, and discovery reads the current provider map on demand rather than observing registry-change events. Provider catalogs return ranked candidates from awaited `list()` calls, where remote providers perform initialization, authentication, and discovery while honoring the lookup abort signal. The registry validates each candidate, resolves same-name skills first-wins by rank, provider registration order, and provider-local order, then sorts summaries by skill name for deterministic consumers. It caches only completed catalog snapshots and retries when a provider/runtime revision changes during discovery, so an unload cannot freeze a stale, unresolvable skill into a session prefix. Runtime `ctx.skills.register(...)` remains a convenience for embedded in-process skills and uses project-over-user priority; `runtime` is reserved as the registry-owned provider name. The local provider scans cwd-sensitive project roots, custom roots, and user roots in first-wins rank order: project `.dsh`, project `.agents`, `customSkillDirs`, user `.dsh`, then user `.agents`. The user `.dsh/skills` scan skips `.system` so a system-owned directory is not treated as normal user content. DeepSeek Harness does not ship built-in system skills; embedded or remote providers supply additional skills when configured. diff --git a/docs/rfc/proposed/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md b/docs/rfc/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md similarity index 52% rename from docs/rfc/proposed/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md rename to docs/rfc/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md index 9a0d9d2cb7..0907a63417 100644 --- a/docs/rfc/proposed/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md +++ b/docs/rfc/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md @@ -1,6 +1,6 @@ # RFC: Drop unconsumed skill provider events -Status: proposed +Status: implemented ## Problem @@ -10,23 +10,18 @@ Skill discovery reads the current provider map on demand, provider registration `tools/change` and `system-prompt/change` are explicitly outside this proposal. Existing simplification decisions retain them as intentional observation points for live tool and prompt UIs, and self-referential mounted plugins already use `tools/change`. This proposal also leaves `subagent/provider-added`/`removed` unchanged because `tool-subagent` has a production lifecycle consumer. -## Proposal +## Decision -Delete the two skill-provider declarations and every emit path, rollback-order branch, test, and generated catalog/matrix row that exists only for them. Remove the corresponding skill-registry README/JSDoc contract. Where tests used an event to observe cleanup, assert provider lookup or collected output instead. +The skill registry declares and emits no provider-membership events. Provider registration and disposal remain direct effect-owned state changes that synchronously invalidate completed catalogs; lookup and discovery read the current provider map on demand. Tests observe cleanup through provider lookup and collected output rather than lifecycle notifications. -Amend the skill-system RFC and package documentation so provider registration is described as direct effect-owned state with cache invalidation, not as a lifecycle notification contract. +The generated event catalog, API catalog, and producer/consumer matrix omit the deleted notifications. The skill-system RFC and package documentation describe registration through its direct effect-owned state and cache-invalidation contract. ## Alternatives considered **Keep skill-provider notifications for future plugins.** A third-party plugin could observe provider availability, but direct provider registration and on-demand lookup are the extension contract; no current consumer needs a push signal. If a future sibling-load race appears, it can introduce a notification with the identity and readiness semantics that consumer requires, as the subagent registry did. -## Acceptance criteria +## Consequences -- The generated event matrix contains no row for `skill/provider-added` or `skill/provider-removed`. -- Skill discovery, direct runtime registration, provider effect rollback/disposal, cache invalidation, and registry lookup cleanup behave unchanged; listener-triggered rollback disappears with the events. -- `tools/change`, `system-prompt/change`, and the real subagent provider lifecycle consumer remain documented and covered. -- Typecheck, coverage, snapshots, doc-sync, module-graph verification, build, and hygiene pass. +The generated event matrix contains no row for `skill/provider-added` or `skill/provider-removed`. Skill discovery, direct runtime registration, provider effect rollback/disposal, cache invalidation, and registry lookup cleanup remain; listener-triggered rollback disappears with the events. `tools/change`, `system-prompt/change`, and the consumed subagent provider lifecycle events are unchanged. -## Risks - -This removes pre-release skill-provider observation points while retaining both ways third-party plugins contribute skills: direct runtime registration and provider registration. A future consumer that needs live provider availability must add a purpose-built notification rather than relying on these generic events. +Pre-release consumers lose skill-provider observation points while retaining both ways to contribute skills: direct runtime registration and provider registration. A future consumer that needs live provider availability must add a purpose-built notification with the identity and readiness semantics it actually requires. diff --git a/packages/cordis/tool-cordis/src/api-catalog.ts b/packages/cordis/tool-cordis/src/api-catalog.ts index b4420e80c7..db54a1924b 100644 --- a/packages/cordis/tool-cordis/src/api-catalog.ts +++ b/packages/cordis/tool-cordis/src/api-catalog.ts @@ -368,18 +368,6 @@ export const EVENT_API: readonly EventApiEntry[] = [ signature: '\'session/flush\'(this: Scoped, session: Session): Promise | void', summary: 'Awaited durability checkpoint.', }, - { - name: 'skill/provider-added', - mode: 'emit', - signature: '\'skill/provider-added\'(provider: SkillProvider): void', - summary: 'A skill provider became resolvable in the `ctx.skills` registry.', - }, - { - name: 'skill/provider-removed', - mode: 'emit', - signature: '\'skill/provider-removed\'(name: string): void', - summary: 'A skill provider left the registry because its plugin fiber was disposed.', - }, { name: 'subagent/end', mode: 'emit', diff --git a/packages/skill/skill/src/index.ts b/packages/skill/skill/src/index.ts index 5ef3f78465..53f291c9dd 100644 --- a/packages/skill/skill/src/index.ts +++ b/packages/skill/skill/src/index.ts @@ -119,23 +119,6 @@ declare module 'cordis' { interface Context { skills: SkillService } - - interface Events { - /** - * A skill provider became resolvable in the `ctx.skills` registry. - * Consumers can observe this instead of depending on Cordis plugin load - * order, which is concurrent for sibling plugins. - * @param provider - the provider that just registered. - * @mode emit - */ - 'skill/provider-added'(provider: SkillProvider): void - /** - * A skill provider left the registry because its plugin fiber was disposed. - * @param name - the registry name that no longer resolves. - * @mode emit - */ - 'skill/provider-removed'(name: string): void - } } interface IndexedCandidate { @@ -196,19 +179,16 @@ export class SkillService extends Service { throw new Error(`a skill provider named "${name}" is already registered`) } const providers = this.providers - const ctx = this.ctx const order = this.nextProviderOrder const invalidateCache = (): void => { this.invalidateCache() } this.nextProviderOrder += 1 - const dispose = ctx.effect(function* () { + const dispose = this.ctx.effect(function* () { providers.set(name, { provider, order }) invalidateCache() yield () => { providers.delete(name) invalidateCache() - ctx.emit('skill/provider-removed', name) } - ctx.emit('skill/provider-added', provider) }, 'skills.registerProvider()') // eslint-disable-next-line @typescript-eslint/no-misused-promises -- synchronous cleanup; direct return preserves disposer identity return dispose