You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(spec,lint)!: accept ADR-0109, remove agent.tools[], lint agent authoring, model AI exposure in tool resolution (#3820) (#3894)
* feat(spec,lint)!: accept ADR-0109, remove agent.tools[], lint agent authoring, model AI exposure in tool resolution (#3820)
Completes the #3820 positioning work. ADR-0109 is now Accepted —
implemented (Phase 1), with evidence: the registry, the reference rule,
cloud#908's conformance tests, and hotcrm#512 as the first app ported to
the model (22 tool references, 0 findings, down from 16 with 10 dead).
BREAKING — `agent.tools[]` is removed. ADR-0064's invariant is "an
agent's tool set is the union of its surface-compatible skills' tools;
nothing falls through to the global registry", and this legacy inline
slot was the one seam that broke it: the runtime resolved
`agent.tools[].name` against the FULL registry with no surface check, so
an `ask`-surface agent could name an authoring tool and get it. Removing
the field makes the invariant structural rather than a rule every reader
must remember (ADR-0049 design+enforce-or-remove). `AIToolSchema` and the
`AITool` type go with it; the json-schema manifest key is deregistered in
the same PR, as its gate requires. Authoring `tools` stays a silent
no-op, not a parse error, so existing stacks keep parsing.
`validate-ai-tool-references` now models AI exposure. It resolved
`action_<name>` against every declared action; the runtime is stricter
(ADR-0011): a tool materialises only for an action that opts in with
`ai.exposed` + `ai.description` AND has a headless path (script/api/flow
with a target or body — url/modal/form are UI-only). The permissive
version blessed references the agent could never call — the exact failure
the rule exists to catch, one layer down. Found while porting HotCRM,
where 3 of the 5 actions its skills wanted are `type:'modal'`. Unresolved
`action_*` names now get their own message and fix, because "not exposed"
and "fictional" need different answers, and the hint says plainly that a
modal action staying human-driven is a design answer, not a gap.
New rule `validate-ai-agent-authoring` (agent-authoring-withdrawn,
warning): flags a stack declaring `stack.agents`. ADR-0063 §2 withdrew
tenant agents and the runtime enforces it on both paths, but
`defineStack` still accepted the array — so an app could ship agents that
parse, validate, and never run (HotCRM did, for months). This is the
missing authoring-time signal; the runtime remains the gate.
Docs: `content/docs/ai/agents.mdx` now teaches the zero-tool-record
default path with a worked Action → skill example, the three conditions
that decide whether `action_<name>` exists, and why naming reasoning
("analyse the pipeline") as a tool is the most common authoring mistake.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4
* docs(ai): use defineAction in the ADR-0109 example (doc-authoring guard, ADR-0059)
`check-doc-authoring.mjs` fails any bare metadata literal in a doc code
block — the new Action → skill example wrote `export const X: Action = {}`.
Switched to `defineAction({ ... })` with its import, which is the pattern
the guard exists to teach.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4
* docs(ai): migrate every agent example from the removed tools[] to skills
The prose-example gate caught two `os:check`-marked agent examples still
authoring `agent.tools` after this PR removed the field; three more
unmarked examples on the same page taught it too. All five now express
capability through `skills`, which is the point — these are examples an
AI copies verbatim, so leaving them on a slot that no longer exists would
have taught the removed pattern to the next generated app.
Each rewrite also names WHERE the tools come from (platform data tools,
`action_<name>` from an `ai.exposed` Action, `search_knowledge`), so the
examples teach the ADR-0109 default path rather than just dropping a
field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4
* chore(i18n): drop the agent.tools form labels the removed field left behind
`check-i18n-bundles` flagged 4 drifted platform-objects bundles: the
metadata-form translations still carried a `tools` entry ("Tools" /
"Herramientas" / "ツール" / "工具", each with the "legacy mode" helpText)
for a form field this PR removed. Regenerated with
`scripts/check-i18n-bundles.mjs --write`; the diff is exactly those four
deletions, one per locale.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4
---------
Co-authored-by: Claude <noreply@anthropic.com>
feat(spec,lint)!: remove `agent.tools[]`, lint agent authoring, and resolve `action_<name>` only when it actually materialises (#3820, ADR-0109 accepted)
7
+
8
+
**Breaking — `agent.tools[]` is removed.** ADR-0064's central invariant is
9
+
"an agent's tool set is the union of its surface-compatible skills' tools;
10
+
nothing falls through to the global registry", and this legacy inline slot
11
+
was the one seam that broke it: the runtime resolved `agent.tools[].name`
12
+
against the **full** tool registry with no surface check, so an `ask`-surface
13
+
agent could name an authoring tool and get it. Removing the field makes the
14
+
invariant structural — there is no second slot to disagree with the skills —
15
+
rather than a rule every reader has to remember (ADR-0049 "design+enforce or
16
+
remove"). `AIToolSchema` / the `AITool` type go with it.
17
+
18
+
*Migration:* attach capability through `skills`. An agent authoring `tools` is
19
+
not a parse error — Zod strips the unknown key — so existing stacks keep
20
+
parsing, but the slot no longer does anything.
21
+
22
+
**`validate-ai-tool-references` now models AI exposure.** The rule previously
23
+
resolved `action_<name>` against every declared action. The runtime is far
24
+
stricter (ADR-0011): it materialises a tool only when the action opts in with
25
+
`ai.exposed: true` + `ai.description`**and** has a headless path (type
26
+
`script`/`api`/`flow` with a target or body — `url`/`modal`/`form` are
27
+
UI-only). Resolving against all actions therefore blessed references the agent
28
+
could never call — the exact failure the rule exists to catch. Unresolved
29
+
`action_*` references now get their own message and fix, since "the action
30
+
isn't exposed" and "the name is fictional" need different answers.
Copy file name to clipboardExpand all lines: docs/adr/0109-ai-tool-authoring-model.md
+5-2Lines changed: 5 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,7 @@
1
1
# ADR-0109: The AI tool authoring model — the default third-party path needs no tool records
2
2
3
-
**Status**: Proposed (2026-07-28; revised same day — see Revision note). Phase 1 (platform tool-name registry + advisory reference lint) is implemented alongside this revision; Phase 2 (the optional refinement layer) awaits acceptance.
3
+
**Status**: **Accepted — implemented (Phase 1)** (2026-07-28; revised same day before acceptance — see Revision note).
4
+
Evidence: `packages/spec/src/system/constants/platform-tool-names.ts` (registry + `platform-tool-names.test.ts`); `packages/lint/src/validate-ai-tool-references.ts` (+ `.test.ts`, wired into `REFERENCE_INTEGRITY_RULES`); conformance in `../cloud``service-ai`/`service-ai-studio` (`platform-tool-names-conformance.test.ts`, cloud#908). First app on the model: `../hotcrm` skills — 22 tool references, 0 findings, down from 16 references with 10 dead (hotcrm#512). Phase 2 (the optional refinement layer) remains open and is gated on a real refinement need.
4
5
**Deciders**: ObjectStack Protocol Architects
5
6
**Builds on**: [ADR-0063](./0063-two-kernel-agents-skills-are-the-extension-primitive.md) (skills + tools are the third-party extension primitive), [ADR-0064](./0064-tool-scoping-to-agent.md) (an agent's tools are its skills' tools), [ADR-0078](./0078-no-silently-inert-metadata.md) (no silently inert metadata), [ADR-0049](./0049-no-unenforced-security-properties.md) (enforce-or-remove)
@@ -130,5 +131,7 @@ lint could not see: no reference rule could be correct in either direction
130
131
-[x]`PLATFORM_PROVIDED_TOOL_NAMES` + `PLATFORM_TOOL_FAMILY_PREFIXES` + invariant tests (spec — this change).
131
132
-[x]`validate-ai-tool-references`, advisory, in `REFERENCE_INTEGRITY_RULES` (lint — this change).
132
133
-[x]`'tools'` into `composeStacks`' concat list (spec — this change).
133
-
-[ ] Registry conformance tests in `../cloud``service-ai` / `service-ai-studio` (cloud PR; activates fully when the cloud `.objectstack-sha` pin advances past this change).
134
+
-[x] Registry conformance tests in `../cloud``service-ai` / `service-ai-studio` (cloud#908; feature-detected, activates when the `.objectstack-sha` pin advances past Phase 1).
135
+
-[x] The lint rule resolves `action_<name>` only for actions that ACTUALLY materialise — `ai.exposed` + `ai.description` + a headless type (ADR-0011). Resolving against every declared action blessed references the agent could never call; caught while porting HotCRM, where 3 of 5 referenced actions are `type:'modal'` (UI-only).
136
+
-[x] First app ported: `../hotcrm` (hotcrm#512) — 10 fictional tools removed, the two withdrawn agents deleted, state changes routed through `action_<name>`.
134
137
-[ ]**Phase 2 (on acceptance + a real refinement need):**`binding` on `ToolSchema`; flow exposure; boot-mirror provenance guard; revisit `tool` metadata-type flags; ratchet the lint rule to error per ADR-0078 once the registry has soaked a release.
0 commit comments