Recording a decision so the next person does not re-derive it — and so the reasoning is on file if the trade-off changes.
The context
objectstack-ai/objectstack#3888 added ai.agents.* and ai.pendingActions.* to @objectstack/client, closing six of the ten AI routes the SDK could not reach. objectstack-ai/cloud#909 flipped the matching ledger rows to sdk and drove two gap ratchets to zero.
The obvious next step looked like: replace objectui's hand-built AI URLs with client.ai.*. Six call sites —
agents — packages/plugin-chatbot/src/useAgents.ts, agentAliases.ts, packages/app-shell/src/hooks/useAiSurface.ts, packages/app-shell/src/console/ai/AiChatPage.tsx, plugin-chatbot's useObjectChat
pending actions — packages/plugin-chatbot/src/usePendingActions.ts, ChatbotEnhanced.tsx, apps/console/src/pages/system/AiPendingActionsPage.tsx
Why we are not doing it
Three facts, found while scoping it:
-
No objectui package depends on @objectstack/client — or on any @objectstack/* package. plugin-chatbot's 19 dependencies are all @object-ui/* and third-party. data-objectstack is the one adapter that bridges to the SDK, and it is a separate package precisely so the UI packages do not take that dependency. Adopting client.ai.* in the hooks would make this the first framework runtime dependency in the UI layer — inverting a deliberate boundary, not tidying one.
-
usePendingActions.ts documents the constraint in its own header: "Pure React + fetch — no extra deps so it stays inside plugin-chatbot's tiny bundle." The package ships at 179.93 KB / 42.67 KB gzipped today.
-
The bridge route is not cheap either. plugin-chatbot's hooks receive no DataSource or adapter (no useAdapter anywhere in the package), and data-objectstack exposes no ai surface. Routing these calls through the adapter would mean adding ai there and threading a DataSource into hooks that currently take none.
What we would be buying
Mostly URL-drift protection: if objectui called client.ai.*, cloud's reachability sweep — which drives the real SDK against the routes its builders return — would transitively cover objectui's URLs too.
That is real, but it is the second copy of a guarantee. The routes themselves are already audited on the cloud side (packages/service-ai/src/ai-route-ledger.ts + its conformance test, objectstack-ai/cloud#903/#906), and the class of bug that started this work — a URL nothing mounts — is caught there for every ai.* method regardless of who calls it.
What is still worth doing, cheaply
usePendingActions.ts hand-copies two wire types (PendingActionStatus, PendingActionRow) that the framework now exports as AiPendingActionStatus / AiPendingAction. A type-only import would delete the mirror at zero bundle cost (types are erased), though it would still be the first @objectstack/* entry in the package — as a devDependency.
Not doing that here either; noting it as the cheapest available increment if the mirror ever drifts.
Revisit if
- objectui's UI packages take an SDK dependency for some other reason — then this becomes free.
- The pending-action or agent wire shapes change and the hand-copied types drift (the mirror above is the early-warning canary).
data-objectstack grows an ai surface for unrelated reasons — then the bridge route costs only the threading.
Recording a decision so the next person does not re-derive it — and so the reasoning is on file if the trade-off changes.
The context
objectstack-ai/objectstack#3888 added
ai.agents.*andai.pendingActions.*to@objectstack/client, closing six of the ten AI routes the SDK could not reach. objectstack-ai/cloud#909 flipped the matching ledger rows tosdkand drove two gap ratchets to zero.The obvious next step looked like: replace objectui's hand-built AI URLs with
client.ai.*. Six call sites —agents —
packages/plugin-chatbot/src/useAgents.ts,agentAliases.ts,packages/app-shell/src/hooks/useAiSurface.ts,packages/app-shell/src/console/ai/AiChatPage.tsx,plugin-chatbot'suseObjectChatpending actions —
packages/plugin-chatbot/src/usePendingActions.ts,ChatbotEnhanced.tsx,apps/console/src/pages/system/AiPendingActionsPage.tsxWhy we are not doing it
Three facts, found while scoping it:
No objectui package depends on
@objectstack/client— or on any@objectstack/*package.plugin-chatbot's 19 dependencies are all@object-ui/*and third-party.data-objectstackis the one adapter that bridges to the SDK, and it is a separate package precisely so the UI packages do not take that dependency. Adoptingclient.ai.*in the hooks would make this the first framework runtime dependency in the UI layer — inverting a deliberate boundary, not tidying one.usePendingActions.tsdocuments the constraint in its own header: "Pure React + fetch — no extra deps so it stays insideplugin-chatbot's tiny bundle." The package ships at 179.93 KB / 42.67 KB gzipped today.The bridge route is not cheap either.
plugin-chatbot's hooks receive no DataSource or adapter (nouseAdapteranywhere in the package), anddata-objectstackexposes noaisurface. Routing these calls through the adapter would mean addingaithere and threading a DataSource into hooks that currently take none.What we would be buying
Mostly URL-drift protection: if objectui called
client.ai.*, cloud's reachability sweep — which drives the real SDK against the routes its builders return — would transitively cover objectui's URLs too.That is real, but it is the second copy of a guarantee. The routes themselves are already audited on the cloud side (
packages/service-ai/src/ai-route-ledger.ts+ its conformance test, objectstack-ai/cloud#903/#906), and the class of bug that started this work — a URL nothing mounts — is caught there for everyai.*method regardless of who calls it.What is still worth doing, cheaply
usePendingActions.tshand-copies two wire types (PendingActionStatus,PendingActionRow) that the framework now exports asAiPendingActionStatus/AiPendingAction. A type-only import would delete the mirror at zero bundle cost (types are erased), though it would still be the first@objectstack/*entry in the package — as adevDependency.Not doing that here either; noting it as the cheapest available increment if the mirror ever drifts.
Revisit if
data-objectstackgrows anaisurface for unrelated reasons — then the bridge route costs only the threading.