Skip to content

Commit 4a3b186

Browse files
feat(objectql)!: an agent and a skill hold one name per package (bare-name phase 1, 4/4) (#31375)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XAtn2B9TgkenAcW63kQpRgC)_ Fixes #30803 PR 4 of 4, the last of phase 1's series by type family: PR 1 #30836 (the data family), PR 2 #31006 (the request family), PR 3 #31093 (the flow family), this one the AI family (`agent`, `skill`), per ruling 6688041914. Claim 6716532253 (`domain:server#5`, card-thread session_01FEkQ5pr7Ua2kPRCMZNupZg). Clause-②: no (narrowing) — the engine's package door (`SchemaRegistry.installPackage`) and item seam (`SchemaRegistry.registerItem`) refuse, after the cold boot, a package registration they accepted before: one whose agent or skill bears a name another installed package holds. Nothing is widened. Metadata-protocol: narrowed Skills-docs: none — `skills/**` does not teach that two packages may share an agent or skill name, and the naming rule itself is phase 2's (#30805); the prescription ships in the refusal and warning text and in ADR-0048's 2026-11-06 addendum (P1.9). ⚠️ **Tier H**: this diff touches `docs/adr/0048-cross-package-metadata-collision.md` (header, §3.4 note, the addendum's status line, P1.5 and the new P1.9). The seat routes the approver. ## 维护者速读 - 改了什么:裁决 C 第一阶段的最后一族。冷启动结束后,agent 和 skill 的名字一个部署只认一个包:安装门和注册口拒绝第二个包,422 `NAMESPACE_CONFLICT`,点出两个持有包和改名建议(与 #30836、#31006、#31093 同一机制)。这是该系列的收尾,合并即关闭 #30803。 - 为什么改:实测两包同名、两种注册顺序——`/meta/agent/NAME` 给先注册包的 agent;MCP 客户端按名取 skill 提示词,拿到的是先注册包的 skill(另一个被静默丢弃),`/meta/skill/NAME` 同样;都不告警。启动后再装一个同名包,今天照常 200 并被遮蔽。 - 风险与回滚:只收窄接受集,不改任何已存数据;`tool`、`rag_pipeline` 不在裁决列表里,不受影响。示例应用和平台包都没有声明 agent 或 skill,现有部署不会因此启动失败(冷启动只告警)。回滚即 revert 本 PR。 - 先看:`packages/objectql/src/bare-name-one-holder.ts`(列表末尾加入 AI 族、两个类型的理由短句)、ADR-0048 P1.9 与 P1.5、`registry-bare-name-one-holder-ai-family.test.ts`。 - 两分钟验证:`pnpm --filter @objectstack/objectql exec vitest run src/registry-bare-name-one-holder-ai-family.test.ts` ——预期 26 passed。 - 需要你做的:ADR 改动是 Tier H,需要审批人批准后由席位落地。 ## What changes - **`packages/objectql/src/bare-name-one-holder.ts`** — the covered types grow from ten to twelve: `agent` and `skill` join `BARE_NAME_ONE_HOLDER_TYPES` last, as the AI family (`BARE_NAME_REGISTRY_TYPES` stays derived); their collection keys (`agents`, `skills`), consumer clauses and nouns, empty built-in and overridable-baseline sets. Module doc: the two measurements, the family closing the series. - **`packages/objectql/src/registry.ts`**, **`packages/objectql/src/plugin.ts`** — comments name the twelve types; no code change. Both doors and the cold-boot seal are generic over the list. - **`packages/objectql/src/registry-bare-name-one-holder-flow-family.test.ts`** — its type-list cell now pins its own family's slot (`slice(0, 10)`); the whole list is pinned by the AI family's file. - **`docs/adr/0048-cross-package-metadata-collision.md`** (Tier H) — header Addenda entry and §3.4's 2026-11-06 note name the twelve types and drop "to follow"; the addendum's status line names P1.9 as the last change; P1.5 becomes the series' record ("The series, complete"); new **P1.9** — the measurement, what is refused, what is not in the list (`tool`, `rag_pipeline`), the cold boot, the series' close. - **`scripts/adr-anchors/packages__objectql__src__bare-name-one-holder.ts.json`** — the invariant names the fourth family and its served holder. - **`.changeset/30803-bare-name-ai-family.md`** — `@objectstack/objectql` `minor`, both declaration lines, BREAKING Why / Before / Now / The cold boot / Who moves / Unchanged, `adr-0087: not-required (no-migration-prescription)`, measured producers. ## Why (measured) Booted showcase + two `AppPlugin` packages (`com.qa.bare.alpha`, `com.qa.bare.beta`) sharing one name per type, in both registration orders, on `30e0bd16`; then `POST /marketplace/install-local` of a third package (`gamma`) declaring all held names (a temporary dogfood file, deleted): | Type | Consumer | Served (alpha→beta / beta→alpha) | Warned | Third package after the boot | |:--|:--|:--|:--|:--| | `agent` | `GET /meta/agent/NAME` | `alpha` / `beta` — first-registered | no | `200`, shadowed | | `agent` | kernel `metadata` service (`get('agent', NAME)`, the MCP `agent_prompt` read) | nothing in either order: the service lists no package agent on the composed runtime (ADR-0063 §2) | — | — | | `skill` | MCP `prompts/get` (`listSkillPrompts` over the merged `GET /meta/skill` list) | `alpha` / `beta` — first-registered; the other row dropped | no | `200`, shadowed | | `skill` | `GET /meta/skill/NAME` | `alpha` / `beta` — first-registered | no | — | | `tool` (not in the ruling) | `GET /meta/tool/NAME` | `alpha` / `beta` — first-registered | no | `200`, shadowed | | `rag_pipeline` (not in the ruling) | — | not declarable: `defineStack` refuses the `ragPipelines` key; nothing registered | — | — | The agent runtime that resolves an agent or a tool by name for chat lives in objectstack-ai/cloud and is NOT MEASURED here. Both ruled types resolve without package context, so both join the list. ## Verification At head `15ed46b6` (feat `55c70231` cut on `30e0bd16`, then `origin/main` merged once at `e7354255`: six commits, none in this PR's files, no conflict). The unit and dogfood pins, the gates and the battery ran on `15ed46b6`; the ablation, the typechecks and the full objectql suite ran on `55c70231`: - `packages/objectql/src/registry-bare-name-one-holder-ai-family.test.ts` (new, 26 cells, real `ObjectKernel` + `ObjectQLPlugin`): the type list (all twelve, in family order) and the two labels; `declaredBareNames` over `agents` and `skills` and a nested plugin, a tool declaring nothing; for each of the two types the data family's matrix — package door (both holders, rename, no record left), ABLATION before the seal, item seam, nested plugin, same-package reload (CONTROL), uninstall releases, environment registration never judged and never a holder (CONTROL), the cold boot (warns once naming both and the rename; reload keeps both, a third refused naming both; `'refuse'` refuses with the doors' envelope); CONTROLs — two packages' tools under one name both install after the seal, one package's agent and another's skill under one name never meet; the reason names only the types it carries; a built-in skill name is refused at the package door. With the data, request and flow families' pins and `hook-binder.test.ts`: **5 files / 180 passed**. - `packages/qa/dogfood/test/bare-name-one-holder-ai-family.dogfood.test.ts` (new, booted showcase + two `AppPlugin` packages + `MarketplaceInstallLocalPlugin`): the cold boot boots and logs ONE `WARN` line naming the agent and the skill, both packages and the renames, never the shared tool; `/meta/agent/NAME`, `/meta/skill/NAME` and the MCP skill prompt keep serving `alpha`; `POST /marketplace/install-local` of `gamma` declaring the held agent name and of `delta` declaring the held skill name are each `422 PLUGIN_REGISTER_FAILED` naming both holders and the rename, the package list unchanged; CONTROL: `epsilon` with fresh agent and skill names plus the shared tool name installs, and its agent and skill prompt serve. With the three earlier families' dogfood files: **4 files / 16 passed**. - Ablation (committed first, through `ablation-replace.mjs`, restored with blob == HEAD and `git diff HEAD` empty): `agent` and `skill` filtered out of `BARE_NAME_ONE_HOLDER_TYPES` (marker `ABL4`) → the AI family's unit file **18 of 26 red** (the list and `declaredBareNames` cells, every refusal at both doors, each ABLATION cell's after-seal half, the cold-boot warn / reload / refuse cells, the reason and built-in cells; the 8 green are, per type, the same-package reload, the uninstall release and the environment CONTROL, plus the tool and agent-versus-skill CONTROLs); objectql rebuilt with the mutation (`ablation-dist-preflight`: marker in 4 built files; the DTS step failed on the mutated literal's type, TS2322, after the JS bundles were written) → the dogfood pin **3 of 4 red** (no warning line; `gamma` and `delta` installed), the CONTROL green. Restored, rebuilt (marker absent from all 14 built files, tree clean) → 4 of 4 green. - `pnpm --filter @objectstack/objectql typecheck` exit 0 (incl. `check:test-typecheck`: 39 files / 233 errors / 64 pinned signatures held, unchanged); `pnpm --filter @objectstack/dogfood exec tsc --noEmit` exit 0; eslint (`--no-inline-config`) over `packages/objectql/src` and the dogfood file exit 0, no findings. - `@objectstack/objectql` full suite under `os-verify-lock`: **568 files / 11516 tests passed** (`VERDICT command-exit 0`). - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `15ed46b6` (merge base `e7354255`, 9 paths): 90 derived families, every one run with its exit code captured before any pipe, all 90 exit 0 on the first run (the `check:dual-build-cjs-loads` prerequisites were already built on this worktree); `--ran`: "90 derived famil(ies) accounted for — 90 run, 0 NOT-MEASURED" (exit 0). The gates first ran on `55c70231`; `main` moved during that run, so it was stopped, `main` merged and the whole derived list re-run on `15ed46b6` (the derivation was the same 90). - Full dogfood battery (`pnpm --filter @objectstack/dogfood test`, under `os-verify-lock`) at `15ed46b6`: `VERDICT command-exit 0` · Test Files 438 passed | 3 skipped (441) · Tests 3622 passed | 41 skipped (3663) · no failures (held the lock 1801s on a shared box). Ran because the title carries `!` and `Clause-②` carries the `(narrowing)` arm. ## Kept - Same-package re-registration, the environment catalog (ADR-0005 overlay), every data-, request- and flow-family pin green unchanged; `tool` and `rag_pipeline` keep §3.4 as written; cold boot stays ruling B (`BARE_NAME_COLD_BOOT_MODE = 'warn'`, untouched). ## Acceptance notes - `agent`: the open framework's one by-name read of a package agent is `/meta/agent/NAME`. The kernel `metadata` service (what MCP `agent_prompt` reads) lists no package agent at all on the composed runtime; agents are platform-owned (ADR-0063 §2), and the cloud runtime that resolves one by name for chat was not measured. The ruling kept `agent` in this card and the `/meta` door is a by-name read without package context, so it joins. - `tool`: same first-registered answer at `/meta/tool/NAME`; not in the ruling's nine, so not added (reported to the seat). - `rag_pipeline`: `defineStack` refuses `ragPipelines` as an unrecognised stack key, though the engine's collection loop and the manifest spelling table still list it; nothing measured beyond that (reported to the seat). ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; lint — see Verification. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request (the fourth of a series by type family, per the ruling). - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: n/a. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English (the maintainer brief is in Chinese by the seat's convention). - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01FEkQ5pr7Ua2kPRCMZNupZg --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 77a675d commit 4a3b186

9 files changed

Lines changed: 647 additions & 25 deletions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/objectql': minor
3+
---
4+
5+
feat(objectql)!: an agent and a skill hold one name per package — once the cold boot has sealed the registry, a package declaring one under a name another installed package holds is refused at install and registration, naming both holders and the rename; a boot that already holds such a clash warns. This closes phase 1 of the bare-name series.
6+
7+
Clause-②: no (narrowing) — the engine's package door (`SchemaRegistry.installPackage`) and item seam (`SchemaRegistry.registerItem`) refuse, after the cold boot, a package registration they accepted before: one whose agent or skill bears a name another installed package holds. Nothing is widened.
8+
Metadata-protocol: narrowed
9+
Skills-docs: none — `skills/**` does not teach that two packages may share an agent or skill name, and the naming rule itself is phase 2's (#30805); the prescription ships in the refusal and warning text and in ADR-0048's 2026-11-06 addendum (P1.9).
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) the refusal removes no key, export or field and changes the shape of no stored body; what an author does about a refused name is pick a different one, which no conversion can choose for them -->
12+
13+
**BREAKING** accept-set narrowing at the package registration door, the fourth and last family of maintainer ruling C on #30798, phase 1 (picklists, mappings and datasets; then APIs, object-less actions and functions; then flows, webhooks, connectors and email templates): managed install (`install-local`, a post-start `manifest.register`, a hot reload) and package registration (`ObjectQL.registerApp` and every package-bound `registerItem`), for agents and skills. ADR-0048 §3.4 narrowed (addendum 2026-11-06, P1.9).
14+
15+
- **Why.** Measured on a booted showcase with two packages sharing one agent name and one skill name, in both registration orders: `/meta/agent/NAME` served the first-registered package's agent, and an MCP client's `prompts/get` of the skill resolved the first-registered package's skill (`listSkillPrompts` keeps the first row per name), as did `/meta/skill/NAME`. Each answer flipped with registration order, and neither warned. A third package installed after the boot under either name installed (`200`) and was shadowed.
16+
- **Before.** Two installed packages could each ship an agent or a skill under one name, at boot or by a later install; every caller was served one of them, chosen by registration order.
17+
- **Now.** After the cold boot (the engine plugin seals the registry at `kernel:bootstrapped`), a package registration declaring an agent or a skill under a name another installed package holds is refused ahead of every mutation, so it leaves no record behind. The envelope is the first family's: `code: 'NAMESPACE_CONFLICT'`, `status: 422`, `install-local` answering `422 PLUGIN_REGISTER_FAILED`, every conflicting name in one refusal, both holders named, the rename (the incoming package's namespace prefixed) carried, `conflicts[]` structured.
18+
- **The cold boot.** A deployment whose installed packages already share such a name still boots: the one warning line names every such name, both holders and the rename, and changes nothing about which holder each caller is served (maintainer ruling 2026-11-06: the boot warns, only a new holder is refused; `BARE_NAME_COLD_BOOT_MODE` stays `'warn'`).
19+
- **Who moves.** An author whose package declares an agent or a skill under a name another package installed beside it already ships, at the next install or upgrade into a running deployment. **The one-line fix: rename the item in one of the two packages — the refusal names the namespace-prefixed name — or uninstall one of them.** An MCP client, an app or a skill that named the old name must name the new one; nothing rewrites stored references.
20+
- **Unchanged.** The same package registering its own name again. An environment item under a package's name (never judged, never a holder: the ADR-0005 overlay). One package's agent and another package's skill under one name (two types). A `tool` and a `rag_pipeline`, which the ruling does not list, and every other type the rule does not list (ADR-0048 §3.4 as written). `OS_METADATA_COLLISION=warn` does not downgrade the refusal.
21+
22+
**Measured producers.** On objectstack `30e0bd16`, no example app and no platform package declares an agent or a skill (`app-showcase`'s coverage file defers both, ADR-0063), so no shipped name has two holders. HotCRM is not checked out in this container and is NOT MEASURED here. Deployed and marketplace packages NOT MEASURED.

‎docs/adr/0048-cross-package-metadata-collision.md‎

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
**Builds on**: [ADR-0003](./0003-package-as-first-class-citizen.md) (package as first-class citizen), [ADR-0005](./0005-metadata-customization-overlay.md) (artifact vs runtime overlay precedence), [ADR-0008](./0008-metadata-repository-and-change-log.md) (metadata repository, `MetaRef` identity), [ADR-0010](./0010-metadata-protection-model.md) (package provenance / `_packageId` stamping)
66
**Consumers**: `@objectstack/objectql` (`SchemaRegistry.registerItem`, `ObjectQL.registerApp`, install path), `@objectstack/console` / `objectui` (routing + metadata resolution), package authors, CLI/CI install path, the app marketplace registry.
77
**Surfaced by**: ADR-0046 review (doc naming) → generalised to all bare-named metadata → re-examined through the app-marketplace install lens.
8-
**Addenda**: [2026-08-08 — publish-time / marketplace namespace exclusivity](#addendum-2026-08-08-publish-time--marketplace-namespace-exclusivity) (**Accepted** 2026-08-08 — maintainer ruling on #1825, [acceptance comment 5226411687](https://github.com/objectstack-ai/objectstack/issues/1825#issuecomment-5226411687), landed by PR #6671; additive to §3.2, supersedes nothing); [2026-11-06 — bare names resolved without a package hold one package](#addendum-2026-11-06-bare-names-resolved-without-a-package-hold-one-package--34-narrowed) (**Accepted**, ruling C on #30798, phase 1; narrows §3.4 for picklists, mappings, datasets, APIs, object-less actions, engine functions, flows, webhooks, connectors and email templates, supersedes nothing).
8+
**Addenda**: [2026-08-08 — publish-time / marketplace namespace exclusivity](#addendum-2026-08-08-publish-time--marketplace-namespace-exclusivity) (**Accepted** 2026-08-08 — maintainer ruling on #1825, [acceptance comment 5226411687](https://github.com/objectstack-ai/objectstack/issues/1825#issuecomment-5226411687), landed by PR #6671; additive to §3.2, supersedes nothing); [2026-11-06 — bare names resolved without a package hold one package](#addendum-2026-11-06-bare-names-resolved-without-a-package-hold-one-package--34-narrowed) (**Accepted**, ruling C on #30798, phase 1; narrows §3.4 for picklists, mappings, datasets, APIs, object-less actions, engine functions, flows, webhooks, connectors, email templates, agents and skills — phase 1 complete — supersedes nothing).
99

1010
---
1111

@@ -267,28 +267,27 @@ package — stays covered by the `naming/namespace-prefix` lint in `os lint`.
267267
> [Addendum (2026-10-08)](#addendum-2026-10-08-the-security-catalog-holds-one-name-per-deployment--34-narrowed).
268268
269269
> **Narrowed (2026-11-06) — picklists, mappings, datasets, APIs, object-less
270-
> actions, engine functions, flows, webhooks, connectors and email templates are
271-
> out of §3.4.** Their consumers resolve the name with no package — a field's
270+
> actions, engine functions, flows, webhooks, connectors, email templates, agents
271+
> and skills are out of §3.4.** Their consumers resolve the name with no package — a field's
272272
> `picklist`, an import's `mappingName`, a dashboard's `datasetName`, the
273273
> endpoint matcher's one API per name, `/actions/global/NAME`, a flow `script`
274274
> node's function, the automation engine's one armed flow per name, the
275275
> dispatcher's one declared webhook per name, the connector picker's one
276-
> connector per name, a send's `template` — so with two holders every caller is
276+
> connector per name, a send's `template`, `/meta/agent/NAME`, an MCP client's
277+
> skill prompt — so with two holders every caller is
277278
> served one of them, chosen by registration order, measured on a booted
278279
> two-package deployment (#30803). Once the cold boot has sealed the registry, a
279280
> package whose picklist, mapping, dataset, API, object-less action, flow,
280-
> webhook, connector or email template bears a name another installed package
281-
> holds is refused at managed install and at package registration (a built-in's
281+
> webhook, connector, email template, agent or skill bears a name another
282+
> installed package holds is refused at managed install and at package registration (a built-in's
282283
> name, at managed install), and a package whose handlers bring a function
283284
> another package holds is refused at the hook binder's bind, each naming both
284285
> holders and the rename. ⛔ The environment catalog is never a holder: an
285286
> environment item under a package's name is the ADR-0005 overlay. ⛔ Nor is the
286287
> email service's built-in template bundle: an app's same-named template
287288
> overrides it by contract (ADR-0131 D6), so that package holds no template name
288-
> and is refused none. The remaining types the measurement found resolving
289-
> without a package context — `agent` and `skill` — are to follow in the series'
290-
> last change. Every other type, an action bound to an object among them, keeps
291-
> §3.4 as written. See
289+
> and is refused none. Every other type keeps §3.4 as written, among them a
290+
> `tool` and an action bound to an object. See
292291
> [Addendum (2026-11-06)](#addendum-2026-11-06-bare-names-resolved-without-a-package-hold-one-package--34-narrowed).
293292
294293
### 3.5 Namespace rename-on-install is deferred (non-goal)
@@ -956,7 +955,7 @@ first phase.
956955

957956
> **Status of this addendum: Accepted** — it records phase 1 of a maintainer
958957
> ruling. It **narrows §3.4 for more metadata types, one family per change (P1.2,
959-
> P1.7, P1.8), and supersedes no other text**: §§1–6, the 2026-08-08 addendum and
958+
> P1.7, P1.8, P1.9 — the last), and supersedes no other text**: §§1–6, the 2026-08-08 addendum and
960959
> the 2026-10-08 addendum stand verbatim.
961960
>
962961
> **Ruling this addendum records** — #30798, answered by the maintainer
@@ -1063,14 +1062,14 @@ the doors never refuse.
10631062
- **No downgrade.** `OS_METADATA_COLLISION=warn` softens the §3.2 namespace gate
10641063
only.
10651064

1066-
### P1.5 To follow
1065+
### P1.5 The series, complete
10671066

1068-
The same measurement found `agent` and `skill` resolving without a package
1069-
context too. They join P1.2's list in the series' last change under #30803, with
1070-
their own pins, narrowing §3.4 the same way (the maintainer kept all of them in
1071-
#30803 on 2026-11-06). `tool` and `rag_pipeline` share a reader with `agent` and
1072-
were not measured. The request family that was listed here is P1.7; the flow
1073-
family — `flow`, `webhook`, `connector` and `email_template` — is P1.8.
1067+
Phase 1 joined P1.2's list one family per change under #30803, each measured
1068+
first, as the maintainer ordered on 2026-11-06 (all of them kept in #30803): the
1069+
data family (P1.1), the request family (P1.7), the flow family (P1.8) and, last,
1070+
the AI family — `agent` and `skill` (P1.9). P1.9's change closes phase 1. `tool`
1071+
and `rag_pipeline` were never in the ruling's list; P1.9 records what was
1072+
measured of them.
10741073

10751074
### P1.6 Where it is implemented
10761075

@@ -1178,3 +1177,39 @@ and is not judged.
11781177
**The cold boot** reads the four with the registry's other holders, under
11791178
P1.3's switch: the flow precedence warning keeps naming both packages and arming
11801179
one, as before; the bare-name warning names every clash once with its rename.
1180+
1181+
### P1.9 The AI family: `agent`, `skill` — the series' last
1182+
1183+
Measured with P1.1's two packages, in both orders, plus a third package
1184+
installed after the boot (#30803, PR 4):
1185+
1186+
- **`agent`** — `/meta/agent/NAME` serves the FIRST-registered package's
1187+
agent, silently. It is the one by-name read of an agent the open framework
1188+
serves: the kernel `metadata` service, which the MCP `agent_prompt` reads,
1189+
answers no package agent at all on the composed runtime (ADR-0063 §2: agents
1190+
are platform-owned), and the agent runtime that resolves an agent by name for
1191+
chat is the cloud's, not measured here.
1192+
- **`skill`** — an MCP client's `prompts/get` of a skill resolves the
1193+
FIRST-registered package's skill (`listSkillPrompts` keeps the first row per
1194+
name and drops the other), silently; `/meta/skill/NAME` serves the same one.
1195+
ADR-0063 §2 names the skill the third-party extension primitive, so this is
1196+
the family's live collision.
1197+
1198+
A third package installed after the boot under either name installs (`200`) and
1199+
is shadowed. Neither type warns.
1200+
1201+
**What is refused.** Both join P1.2's list: the same two doors, the same
1202+
holders, the same envelope and rename. Neither is bundled, and neither has an
1203+
overridable baseline or a built-in name. An agent and a skill are two types: one
1204+
package's agent and another's skill under one name never meet.
1205+
1206+
**Not in the list.** A `tool` is not covered — the ruling's types do not name it.
1207+
Measured beside them: `/meta/tool/NAME` serves the first-registered package's
1208+
tool as well; the readers that resolve a tool by name are the cloud's. A
1209+
`rag_pipeline` cannot be declared through `defineStack` (the stack schema
1210+
refuses `ragPipelines`) and registered nothing in the measurement. Both are left
1211+
as §3.4 has them.
1212+
1213+
**The cold boot** reads the two with the registry's other holders, under P1.3's
1214+
switch (`'warn'`). This is the series' closing change: phase 1's list is
1215+
complete, and phase 2 (#30804, #30805) and phase 3 (#30806) follow.

‎packages/objectql/src/bare-name-one-holder.ts‎

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
* narrowed 2026-11-06). Each family of covered types joins in its own change
1111
* under #30803: the data family first, then the request family (`api`, the
1212
* object-less `action`, the engine's function registry), then the flow family
13-
* (`flow`, `webhook`, `connector`, `email_template`).
13+
* (`flow`, `webhook`, `connector`, `email_template`), and last the AI family
14+
* (`agent`, `skill`), which closes the series.
1415
*
1516
* ## The rule
1617
*
@@ -52,7 +53,14 @@
5253
* per name, and a second one at boot already refuses the boot (ADR-0097);
5354
* - `email_template` — a send's `template: NAME` resolves the
5455
* FIRST-registered package's bundle, and `/meta/email_template/NAME`
55-
* serves the same one.
56+
* serves the same one;
57+
* - `agent` — `/meta/agent/NAME` serves the FIRST-registered package's
58+
* agent, the one by-name read of an agent the open framework serves (the
59+
* agent runtime that resolves an agent by name is the cloud's);
60+
* - `skill` — an MCP client's `prompts/get` of a skill keeps the
61+
* FIRST-registered package's skill (`listSkillPrompts` keeps the first
62+
* row per name and drops the other), and `/meta/skill/NAME` serves the
63+
* same one.
5664
*
5765
* Every one of them flips with registration order, and none says so (the
5866
* object-less action's handler bind warns that it replaces one, and names
@@ -145,7 +153,8 @@ import { isObjectLessActionKey, standaloneActionOwnerKey } from './action-govern
145153
export type BareNameRegistryType =
146154
| 'picklist' | 'mapping' | 'dataset'
147155
| 'api' | 'action'
148-
| 'flow' | 'webhook' | 'connector' | 'email_template';
156+
| 'flow' | 'webhook' | 'connector' | 'email_template'
157+
| 'agent' | 'skill';
149158

150159
/**
151160
* A covered type: a registry type, or `function` — the engine's function
@@ -171,6 +180,9 @@ export const BARE_NAME_ONE_HOLDER_TYPES: readonly BareNameOneHolderType[] = Obje
171180
'webhook',
172181
'connector',
173182
'email_template',
183+
// the AI family
184+
'agent',
185+
'skill',
174186
]);
175187

176188
/** The covered registry types, in the order a refusal lists them. */
@@ -213,6 +225,8 @@ const COLLECTION_KEY: Readonly<Record<BareNameRegistryType, string>> = Object.fr
213225
webhook: 'webhooks',
214226
connector: 'connectors',
215227
email_template: 'emailTemplates',
228+
agent: 'agents',
229+
skill: 'skills',
216230
});
217231

218232
/**
@@ -232,6 +246,8 @@ export const BUILT_IN_BARE_NAMES: Readonly<Record<BareNameOneHolderType, Readonl
232246
webhook: new Set<string>(),
233247
connector: new Set<string>(),
234248
email_template: new Set<string>(),
249+
agent: new Set<string>(),
250+
skill: new Set<string>(),
235251
});
236252

237253
/**
@@ -259,6 +275,8 @@ export const OVERRIDABLE_BASELINE_PACKAGES: Readonly<Record<BareNameOneHolderTyp
259275
webhook: new Set<string>(),
260276
connector: new Set<string>(),
261277
email_template: new Set<string>(['com.objectstack.service.email']),
278+
agent: new Set<string>(),
279+
skill: new Set<string>(),
262280
});
263281

264282
/**
@@ -383,6 +401,8 @@ const CONSUMER_CLAUSE: Readonly<Record<BareNameOneHolderType, string>> = Object.
383401
webhook: 'the webhook dispatcher subscribes one declared webhook per name',
384402
connector: 'a flow `connector_action` node and the connector picker name a connector',
385403
email_template: 'a send names its email template',
404+
agent: '`/meta/agent/NAME` serves one agent per name',
405+
skill: 'an MCP client fetches a skill\'s prompt by its name',
386406
});
387407

388408
/** The noun a reason names a covered type by. */
@@ -397,6 +417,8 @@ const TYPE_NOUN: Readonly<Record<BareNameOneHolderType, string>> = Object.freeze
397417
webhook: 'a webhook',
398418
connector: 'a connector',
399419
email_template: 'an email template',
420+
agent: 'an agent',
421+
skill: 'a skill',
400422
});
401423

402424
const joinList = (parts: readonly string[]): string =>

‎packages/objectql/src/plugin.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ import {
6767
import { SecurityCatalogNameConflictError, findEnvironmentHeldSecurityCatalogNames } from './registry.js';
6868
// One name, one package holder for the bare-name types (a picklist, a mapping, a
6969
// dataset, an API, an object-less action, a function, a flow, a webhook, a
70-
// connector, an email template) — the cold boot's seal and its one switch
70+
// connector, an email template, an agent, a skill) — the cold boot's seal and its one switch
7171
// (`bare-name-one-holder.ts`, "The cold boot").
7272
import { sealBareNameHoldersAtColdBoot } from './registry.js';
7373
import { BARE_NAME_COLD_BOOT_MODE } from './bare-name-one-holder.js';

0 commit comments

Comments
 (0)