feat(spec,lint)!: accept ADR-0109, remove agent.tools[], lint agent authoring, model AI exposure in tool resolution (#3820) - #3894
Merged
Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…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
marked this pull request as ready for review
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>
This was referenced Jul 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 model — objectstack-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-referencesresolvedaction_<name>against every declared action. The runtime is far stricter (ADR-0011): a tool materialises only when the actionai.exposed: trueandai.descriptionscript/api/flow—url/modal/formare UI-onlytarget(or abody, forscript)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[]removedADR-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[].nameagainst the full registry with no surface check, so anask-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).AIToolSchemaand theAITooltype go with it; the json-schema manifest key is deregistered in the same PR, as that gate requires. Authoringtoolsremains a silent no-op rather than a parse error, so existing stacks keep parsing. Migration: attach capability throughskills.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
defineStackstill 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. JoinsREFERENCE_INTEGRITY_RULES.5. Docs teach the default path
content/docs/ai/agents.mdxgains a worked Action → skill example with nodefineToolanywhere, 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 --noEmitclean ·check:docs,check:skill-refs,check:react-blocks,check:skill-examplesin sync ·check:api-surfacecorrectly flagged both removals as breaking before the snapshot was regenerated (changeset ismajor).@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 reachvalidate/lint/compilethrough the suite.pnpm buildgreen.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