Skip to content

feat(spec,lint)!: accept ADR-0109, remove agent.tools[], lint agent authoring, model AI exposure in tool resolution (#3820) - #3894

Merged
os-zhuang merged 4 commits into
mainfrom
claude/agent-metadata-positioning-th5hhm
Jul 28, 2026
Merged

feat(spec,lint)!: accept ADR-0109, remove agent.tools[], lint agent authoring, model AI exposure in tool resolution (#3820)#3894
os-zhuang merged 4 commits into
mainfrom
claude/agent-metadata-positioning-th5hhm

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Completes the #3820 positioning work: ADR-0109 is accepted, the two structural holes it left open are closed, and a bug in the rule merged by #3885 is fixed.

1. ADR-0109 → Accepted — implemented (Phase 1)

Evidence recorded in the ADR: the registry + reference rule (#3885), conformance tests in cloud (objectstack-ai/cloud#908), and the first app ported to the modelobjectstack-ai/hotcrm#512, which went from 16 tool references with 10 dead to 22 references, 0 findings.

2. Fix: the rule blessed references the agent can never call

validate-ai-tool-references resolved action_<name> against every declared action. The runtime is far stricter (ADR-0011): a tool materialises only when the action

Requirement
opts in ai.exposed: true and ai.description
has a headless path type script / api / flowurl / modal / form are UI-only
is dispatchable a target (or a body, for script)

The permissive version therefore produced a false pass — 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'; the rule had already green-lit my own rewrite before I checked the runtime.

Unresolved action_* names now get a distinct message and fix, because "the action exists but isn't exposed" and "the name is fictional" need different answers. The hint says plainly that a modal action staying human-driven is a design answer, not a gap.

3. Breaking: agent.tools[] 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 — there is no second slot to disagree with the skills — rather than a rule every reader has to 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 that gate requires. Authoring tools remains a silent no-op rather than a parse error, so existing stacks keep parsing. Migration: attach capability through skills.

4. New rule: validate-ai-agent-authoring (warning)

ADR-0063 §2 withdrew tenant agents and the runtime enforces it on both paths (catalog filter + objectstack-ai/cloud#904's load gate) — 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 (ADR-0078: loud at the producer, tolerant at the consumer); the runtime remains the gate, which is why it warns rather than errors — a platform package legitimately authors agent records and the rule can't tell which is which from the stack alone. Joins REFERENCE_INTEGRITY_RULES.

5. Docs teach the default path

content/docs/ai/agents.mdx gains a worked Action → skill example with no defineTool anywhere, the three conditions above as a table, and the point that naming reasoning ("analyse the pipeline", "score this lead") as a tool is the most common authoring mistake — it produces an assistant that claims abilities it does not have.

Verification

  • @objectstack/spec: 262 files / 6817 tests · tsc --noEmit clean · check:docs, check:skill-refs, check:react-blocks, check:skill-examples in sync · check:api-surface correctly flagged both removals as breaking before the snapshot was regenerated (changeset is major).
  • @objectstack/lint: 37 files / 540 tests (11 for the exposure predicate + 6 for the new rule).
  • @objectstack/cli: 72 files / 737 tests — both new rules reach validate/lint/compile through the suite.
  • Full workspace pnpm build green.

Refs #3820 · #3885 · ADR-0109 · ADR-0064 · ADR-0011 · ADR-0078 · ADR-0049

🤖 Generated with Claude Code

https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4


Generated by Claude Code

…uthoring, 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
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 28, 2026 3:24pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests protocol:ai tooling size/l labels Jul 28, 2026
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/lint, @objectstack/platform-objects, @objectstack/spec.

104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/lint, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/platform-objects, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/platform-objects, @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

claude added 3 commits July 28, 2026 15:11
…rd, 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
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
…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
@os-zhuang
os-zhuang marked this pull request as ready for review July 28, 2026 16:02
@os-zhuang
os-zhuang merged commit e2616e0 into main Jul 28, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/agent-metadata-positioning-th5hhm branch July 28, 2026 16:02
os-zhuang added a commit that referenced this pull request Jul 28, 2026
#3894 follow-up) (#3904)

#3894 removed `agent.tools` and `AIToolSchema` outright, which broke
`pnpm --filter @objectstack/spec build` on main: the authorable-surface
ratchet (ADR-0104 / #3733) fails when an authorable key disappears,
because none of these schemas is `.strict()` — Zod silently STRIPS an
unknown key, so an author who keeps writing `tools:` gets a clean parse
and an agent that reaches none of the tools they listed. That is the
silent-capability-loss shape #3820 exists to eliminate, restored one
layer down. The gate was right; my removal was wrong.

(It slipped CI because Build Core restored a turbo cache entry for the
spec build; the failure reproduces on any cold build of main.)

The removal stands — ADR-0064 needs the second, unscoped tool slot gone.
What changes is HOW it is removed:

- `agent.tools` is now `retiredKey()`, so authoring it throws with the
  fix in the message (use `skills`; a platform tool by name, or
  `action_<name>` for your own AI-exposed Action; `os migrate meta`).
  This supersedes #3894's "remains a silent no-op rather than a parse
  error" — loud is correct, and is what the ratchet requires.
- A D2 conversion `agent-tools-to-skills` + its D3 chain step, so the
  removal reaches spec-changes.json, the upgrade guide and the
  `spec_changes` MCP tool. Unlike the protocol-17 renames beside it this
  has NO lossless target: each entry must become a reference inside a
  skill, which is a human decision. So it drops the dead key (the runtime
  stopped reading it in cloud#910, so it already contributes nothing) and
  emits one notice per agent marking where capability must be
  re-declared.
- The three `ai/AITool:*` baseline lines are deleted deliberately — the
  one case the ratchet sanctions in-PR. They were authorable only as the
  element shape of `agent.tools`; with the parent tombstoned nothing
  reaches them, so they cannot vanish silently: the parent speaks first,
  with a prescription. Keeping a schema alive purely to hold three
  unreachable lines would be the dead contract surface this issue is
  about.

Agent tests now pin the rejection and its message rather than the strip
semantics they asserted before.

Verified: spec build OK, spec 6823 tests, lint 540, cli 761, full
workspace build, doc-authoring guard, check:docs, check:api-surface,
check:skill-refs, check:skill-examples (197), eslint — all green.


Claude-Session: https://claude.ai/code/session_01BHjroNkLkajskKbJaidko4

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ai size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants