A shared reference for how GemStack packages are layered and named. The AI family (ai-sdk, ai-skills, ai-autopilot, ai-mcp) and the standalone @gemstack/mcp server framework have all shipped; the design rationale below is kept as the record of why the boundaries are drawn where they are.
The ai- prefix means "depends on the agent runtime." A package about AI that is agent-agnostic does not get the ai- prefix; it is a peer of the AI family, not a member.
GemStack hosts framework-agnostic engines that work in any Node app. It does not host framework-specific bindings or extensions. This is the line that keeps the umbrella legible.
- Engines (belong here):
@gemstack/ai-sdkand its family. They have no hard dependency on any framework. - Bindings (do not belong here): framework extensions like the
vike-*packages (in thevike-datarepo). Their whole value is the framework integration, so they are the opposite of agnostic. They live with their framework and consume GemStack engines, e.g.vike-aiis a thin Vike binding over@gemstack/ai-sdk:
@gemstack/ai-sdk (agnostic engine, here)
^
| thin binding
vike-ai (Vike extension, in vike-data) -- consumes the engine
Packages join GemStack by graduating, one at a time, when they prove framework-agnostic value, not by bulk-moving a framework's package set in.
@gemstack/ai-sdkis the template: it matured inside Rudder as@rudderjs/ai, proved it was agnostic and broadly useful, then graduated to@gemstack/.- A
vike-*package moves here only if a genuinely agnostic core falls out of it that is useful beyond its framework. In that case the core graduates (e.g.@gemstack/<core>) while the framework binding staysvike-*. - Because both repos are co-governed and the
vike-*set sits in the Vike orbit, any such move is decided with the Vike team, when there is brand traction, not unilaterally.
An audit of vike-data shows the agnostic engines are not in the vike-* packages (those are bindings) but in the universal-* packages, which already carry zero Vike imports. These are the real candidates, in priority order after @gemstack/ai-sdk:
| Candidate (today) | Would become | Notes |
|---|---|---|
@universal-orm/core (+ @universal-orm/drizzle / /memory / /rudder) |
@gemstack/orm (+ adapters) |
The ORM analog of @gemstack/ai-sdk. Mature, clearly agnostic. The strongest next candidate; move the core + its adapter family together. |
@vike-data/universal-schema |
@gemstack/schema |
"Usable standalone by any framework or ORM." Agnostic, but currently mis-scoped under @vike-data. |
@vike-data/kit |
(stays) | Agnostic primitives (createPort), but it is the kit for authoring bindings, so it belongs with the binding ecosystem, not the engine umbrella, unless GemStack later wants a shared-primitives package. |
Realized fully, GemStack is the unified home for agnostic engines: @gemstack/ai-sdk (AI), @gemstack/orm (data), @gemstack/schema (schema).
These are not orphaned code needing a home: @universal-orm is already its own deliberate npm scope, and universal-schema sits under @vike-data. So there are three agnostic-ish scopes in play (@gemstack, @universal-orm, @vike-data). The decision is therefore not "where should this code live" but "do we consolidate the agnostic engines under one umbrella (@gemstack), or keep @universal-orm as a parallel brand?" Since @universal-orm is co-developed, this is a decide-with-the-Vike-team call, gated on brand traction.
When a candidate does graduate, follow the @gemstack/ai-sdk playbook exactly: copy the source in, rename to the @gemstack/* name, leave a re-export at the old name, reset to a fresh 0.x line, then repoint dependents. The old-name package takes one of two shapes: a deprecated shim (pure re-export, slated for eventual removal) or — when it has framework-coupled pieces that cannot graduate (provider wiring, ORM-backed stores, CLI) — a living framework binding that re-exports the agnostic core and owns those bindings. @rudderjs/ai is the latter: it re-exports @gemstack/ai-sdk and keeps the Rudder AiProvider, ORM stores, and make:agent / ai:eval CLI. Don't mislabel a binding as deprecated.
@gemstack/ai-sdk agent runtime (the "verbs")
@gemstack/ai-skills capability bundles (the composable "nouns") -> ai-sdk
@gemstack/ai-autopilot orchestration / autonomy (the "director") -> ai-sdk (+ skills)
@gemstack/ai-mcp agent <-> MCP bridge (the "adapter") -> ai-sdk
-----------------------------------------------------------------------------------
@gemstack/mcp standalone MCP server framework agent-agnostic, NOT ai-*
skills, autopilot, and ai-mcp all depend on ai-sdk. ai-sdk depends on none of them. Nothing depends "up." If the arrows are not one-directional, the split is wrong.
- Stays in
ai-sdk: what is intrinsic to running an agent. Providers, the agent loop, tools, streaming, middleware, structured output, basic memory. - Carves out: what has a heavy/optional dependency or a genuinely different consumer.
ai-mcpis the first carve-out (it has its own SDK dependency and a distinct audience).evalis a good later candidate (dev/test-time, different lifecycle). Resist fragmenting into many micro-packages; each carve-out is a peer-dep seam maintained forever.
- skill = a portable, loadable capability bundle (instructions + tools + resources), composed onto an agent on demand. Distinct from a single
tool. (See theboost/skills/SKILL.mdbundles inai-sdkfor the shape.)ai-skillsis the registry + loader + runtime for those bundles. - autopilot = autonomy and orchestration: multi-agent, planning loops, long-running runs, handoffs.
MCP shows up in GemStack in two fundamentally different roles. They point in opposite directions and must not be merged into one package.
| Agent bridge | Server framework | |
|---|---|---|
| Package | @gemstack/ai-mcp |
@gemstack/mcp (agent-agnostic; not ai-*) |
| Rudder origin | @rudderjs/ai/mcp (a subpath today) |
@rudderjs/mcp (a standalone package, mature) |
| What it is | A thin bridge that makes an Agent speak MCP | A full framework for authoring MCP servers |
| Surface | mcpClientTools (consume a remote MCP server's tools as Agent tools) + mcpServerFromAgent (wrap an Agent as an MCP server) |
McpServer, McpTool, McpResource, McpPrompt, decorators (@Name/@Version/@Instructions), OAuth2 middleware, a provider, a test client, a make-mcp-server scaffolder |
| Centered on | the Agent abstraction | your application (a server can expose anything: DB, files, weather; no agent involved) |
| Coupling | depends on ai-sdk; useless without an Agent |
agent-agnostic; does not depend on ai-sdk |
| Use it when | you are exposing an existing Agent, or feeding remote MCP tools into one | you are authoring a server from scratch (tools / resources / prompts / auth) |
Why the AI layer has an inner mcp at all: the bridge only makes sense with the Agent type, and it is optional (gated behind an optional @modelcontextprotocol/sdk peer dependency). So it lives next to the thing it extends, and consumers who never touch it never install the SDK. Forcing every AI user to pull in the whole server framework for two helper functions would be wrong.
The "which MCP do I use?" decision (document this so the two never look like duplicates):
Exposing an existing Agent? Use
ai-mcp. Authoring a server from scratch (tools / resources / prompts / auth)? Usemcp.
There is a tiny surface overlap (both can "produce an MCP server"), but from different inputs: mcpServerFromAgent(anAgent) versus a hand-authored McpServer. That is expected, not duplication.
ai-mcp carve-out (decided - see issue #7)
The bridge is two functions today. Decisions:
- Promote to a package, not keep a subpath. Beyond family symmetry, this makes the optional-dep boundary honest:
@modelcontextprotocol/sdkis a peer ofai-sdktoday, so every AI consumer sees it even though only/mcpuses it. As a package, onlyai-mcpdeclares that peer. It is also the cheapest seam to prove the carve-out mechanism on before applying it to something heavier (eval). - Hard-move, no shim. External importers of
@gemstack/ai-sdk/mcp= 0 (ai-sdk is days old); internal = exactly 1 (@rudderjs/ai/mcp, a shim we control). A re-export shim would also create the cycleai-sdk -> ai-mcp -> ai-sdk, violating the one-directional rule. So: remove the./mcpexport and the@modelcontextprotocol/sdkpeer fromai-sdk(breaking 0.x minor0.2.0 -> 0.3.0), startai-mcpat a fresh0.1.0, repoint the one internal consumer, and add a migration note. Keep the deprecated-shim-for-one-minor play in reserve for the first carve-out after 1.0, where a real installed base exists. - Bridge only. The standalone server framework (
@gemstack/mcp) stays the other taxonomy axis and a separate graduation, not part of this carve-out. Ship the "which MCP do I use?" line (below) inai-mcp's README + npm description so the two never read as duplicates.
The seam is small and one-directional: mcp/* imports one runtime value (dynamicTool) plus four types (Agent, HasTools, Tool, ToolCallContext) from ai-sdk; ai-sdk imports nothing back. Still gated on family alignment with the Vike team before code lands.
ai-skills design (decided - see issue #8)
Largely greenfield: it builds the registry + loader + runtime around the existing boost/skills convention. Decisions:
- Manifest =
SKILL.mdfrontmatter (markdown-first), not a TS-first definition. YAML frontmatter (name,description,trigger,skip,appliesTo,metadata) + a markdown instructions body, exactly asboost/skillsalready ships. This keeps zero divergence from the shipped convention, matches the Anthropic Agent Skills shape (the portability moat: skills authored for Claude load here, gemstack skills ship as plain folders), and makes progressive disclosure fall out for free (index the cheap frontmatter, load the body ontrigger). - Tools = reuse
ai-sdktool()directly. A skill folder isSKILL.md+ an optional co-locatedtools.tsexporting plaintool()objects; the loader imports and merges them. Namespacing / scoping (active only while loaded) is handled by the loader at composition time, not via a new authoring API. One tool API across the framework; skills stay self-contained. Portability note: theSKILL.mdinstructions/resources stay portable across agents, the typedtools.tsis the gemstack-specific binding (Anthropic skills bundle scripts instead) - an expected split, not a problem. - Security = explicit trust boundary, no in-process sandbox. A skill is code you install/author, like any Vite/ESLint plugin; loading it runs its code. The framework makes the boundary honest: no auto-loading of untrusted directories (skills come from explicit, registered/allowlisted sources), surface-before-compose (report a skill's instructions/tools/resources before attaching), and reuse the existing tool-approval/middleware flow for the risky moment (tool execution). A true out-of-process / VM sandbox is out of scope (Node
vmis not a security boundary, the permission model is process-wide) - recommend OS/container isolation around the app if real isolation is needed. - Composition = declarative
skills()class method, mirroringtools()/middleware(). Precedence is unambiguous: the agent's own declarations are authoritative and skills augment + yield on conflict. Instructions: the agent'sinstructions()is the base identity (first/wins), skill instructions composed in after. Tools: union ofagent.tools()+ skill tools in declaration order; on a name collision the agent's own tool wins, with the loader's namespacing as backstop. Progressive disclosure is independent of declaration - you declare the available set, the runtime loads each skill's body/tools lazily ontriggermatch.
Follow-up authoring options (low priority, revisit on demand): TS-first defineSkill (#11), a skill-scoped skillTool() wrapper (#12), and imperative agent.use(skill) runtime composition (#13). Still gated on family alignment before code lands.
ai-autopilot design (direction set, parked - see issue #9)
The most speculative of the family, so this records direction, not a committed API. It sits above the real ai-sdk primitives (asTool, resumeAsTool, resumeManyAsTool, SubAgentRunStore, stop conditions).
- Scope = policy, not mechanism. The primitives are mechanism (transfer control, run a subagent, resume a paused run); autopilot is the reusable control loop deciding which agents run, in what order, how results combine, when to stop, under a budget/guardrail. Enforced rule: if a feature is just calling a primitive, it stays in
ai-sdk;ai-autopilotonly earns its keep as the topology / control-policy / run-lifecycle layer. - Seed slice = Supervisor: plan -> dispatch subagents (fan-out via
asTool/resumeManyAsTool) -> synthesize. The smallest thing clearly more than the primitives; a universally useful topology. Other topologies (pipelines, etc.) come later, on demand. - Durability = in-process first. Reuse the existing
SubAgentRunStore+ resume primitives for pause/resume; no new peer dep in v1. A durable / queue-backed runner is deferred to a concrete long-running use case and should arrive as an optional adapter/peer, not core.
Stays parked as a design sketch: revisit once ai-mcp and ai-skills land and real orchestration use cases emerge. Gated on family alignment before code lands.
The family shipped in the order that proved the dependency seam earliest:
@gemstack/ai-sdk(shipped)@gemstack/ai-mcp(shipped - first carve-out; cheap, and forced the dependency seam to be proven early)@gemstack/ai-skills(shipped)@gemstack/ai-autopilot(shipped)@gemstack/mcp(shipped - the standalone server framework, a separate graduation off@rudderjs/mcp)