SDK Hooks
Last updated April 7, 2026
How plugin handlers talk to platform services — the hook model.
Runtime hooks are how plugin handlers reach platform services: the LLM, the cache, the vector store, logging, analytics, config. They're composables — pattern borrowed from React — that read from a global platform singleton and return typed service instances (or undefined when unconfigured).
This page is the plugin-facing overview. For the full hook API reference — every method, every parameter — see SDK → Hooks.
The one rule
Plugin handlers import hooks from @kb-labs/sdk only. Not from @kb-labs/shared-command-kit, not from @kb-labs/core-platform, not from anywhere else. The SDK re-exports every hook you can use; internal packages are off-limits.
import {
usePlatform,
useLogger,
useLLM,
useCache,
useStorage,
useConfig,
useEmbeddings,
useVectorStore,
useAnalytics,
useEventBus,
} from '@kb-labs/sdk';The rule exists because @kb-labs/core-platform and friends evolve independently of the SDK. The SDK is the stable surface; everything behind it may change between minor versions.
Why hooks instead of ctx.platform.*
The platform context (ctx.platform) does expose the same services directly:
async execute(ctx, input) {
const cache = ctx.platform.cache; // ← works, but don't
if (cache) await cache.set('k', 'v');
}But you shouldn't use this path. Use the hooks:
import { useCache } from '@kb-labs/sdk';
async execute(ctx, input) {
const cache = useCache(); // ← idiomatic
if (cache) await cache.set('k', 'v');
}Three reasons:
- Hooks are versioned through the SDK.
ctx.platform.cachegoes through@kb-labs/plugin-contracts, which is not what plugins should couple to.useCache()is in@kb-labs/sdk, which is. - Hooks handle wrapping automatically. The platform wraps every adapter in analytics, tracing, and rate-limiting layers.
useLLM()returns the fully wrapped adapter;ctx.platform.llmsometimes skips those wrappers depending on the runtime path. - Hooks are testable without a full context. In tests, you can stub out the platform singleton without building a full
PluginContextV3. See SDK → Testing.
The rule of thumb: when a hook exists for a service, use it. Reach for ctx.platform only for services without a dedicated hook (rare — the SDK exports hooks for every important service).
Availability: undefined means "not configured"
Most hooks return T | undefined:
const cache = useCache();
if (cache) {
await cache.set('k', 'v', 60_000);
}undefined means the adapter isn't in platform.adapters — the user hasn't configured it. Handlers should degrade gracefully:
async execute(ctx, input) {
const cache = useCache();
const cacheKey = `query:${input.id}`;
if (cache) {
const hit = await cache.get(cacheKey);
if (hit) return { exitCode: 0, result: hit };
}
const result = await expensiveQuery(input.id);
if (cache) {
await cache.set(cacheKey, result, 300_000);
}
return { exitCode: 0, result };
}The handler runs fine with or without a cache — when present, it's faster; when absent, it falls back to computing every time.
Exception: useLogger is always defined
The one hook that never returns undefined:
const logger = useLogger();
logger.info('task started', { taskId }); // always worksThe platform guarantees a logger exists — even if nothing is configured, it falls back to a console logger. You don't need a null check.
The hook catalog
Quick reference. See SDK → Hooks for every method and option.
| Hook | Returns | Nullable | Use for |
|---|---|---|---|
usePlatform() | full platform singleton | no | accessing services that don't have dedicated hooks |
useLogger() | ILogger | no | structured logging |
useLLM(options?) | ILLM | yes | text generation, streaming, tool calls |
useCache() | ICache | yes | KV, sorted sets, atomic ops |
useStorage() | IStorage | yes | durable blob/file storage |
useVectorStore() | IVectorStore | yes | semantic search, upsert, filter |
useEmbeddings() | IEmbeddings | yes | text → vector conversion |
useAnalytics() | IAnalytics | yes | event tracking |
useEventBus() | IEventBus | yes | pub/sub across plugins |
useConfig<T>(section?) | T | undefined | yes | plugin config from kb.config.json |
usePlatform() — when nothing else fits
Returns the full platform object if you need a service that doesn't have its own hook (rare):
import { usePlatform } from '@kb-labs/sdk';
const platform = usePlatform();
const customAdapter = platform.getAdapter?.('myCustomService');Prefer the dedicated hooks when they exist. usePlatform is a fallback.
Hook composition
Hooks can call other hooks. A common pattern is using useConfig to read plugin config, then passing pieces to other hooks:
import { useConfig, useLLM, useCache } from '@kb-labs/sdk';
interface MindConfig {
defaultTier: 'small' | 'medium' | 'large';
cacheTtl: number;
}
async execute(ctx, input) {
const config = await useConfig<MindConfig>();
const llm = useLLM({ tier: config?.defaultTier ?? 'small' });
const cache = useCache();
if (!llm) {
return { exitCode: 1, error: { message: 'LLM not configured' } };
}
const cacheKey = `mind:${input.query}`;
if (cache) {
const hit = await cache.get(cacheKey);
if (hit) return { exitCode: 0, result: hit };
}
const answer = await llm.complete(input.query);
const result = { answer: answer.text, model: llm.model };
if (cache) {
await cache.set(cacheKey, result, config?.cacheTtl ?? 60_000);
}
return { exitCode: 0, result };
}Each hook handles its own concerns. The handler orchestrates.
Testing with hooks
Hooks read from the platform singleton. In tests, you don't need to build a full context — you can replace the singleton:
import { createTestContext } from '@kb-labs/sdk/testing';
import { describe, it, expect, vi } from 'vitest';
import handler from './my-handler';
describe('my-handler', () => {
it('uses the cache when available', async () => {
const mockCache = {
get: vi.fn().mockResolvedValue(null),
set: vi.fn().mockResolvedValue(undefined),
// ... other ICache methods
};
const ctx = createTestContext({
platform: { cache: mockCache },
});
await handler.execute(ctx, { id: '123' });
expect(mockCache.get).toHaveBeenCalledWith('query:123');
expect(mockCache.set).toHaveBeenCalled();
});
});createTestContext builds a PluginContextV3 with a platform that useCache() and friends will read from. See SDK → Testing for full patterns.
What about the Studio side?
The hooks on this page are for plugin handlers — the code that runs inside CLI commands, REST routes, workflow steps, webhook receivers. The Studio side has its own set of hooks (useData, useMutateData, useSSE, useWebSocket, useNavigation, ...) imported from @kb-labs/sdk/studio.
Don't mix them. Handler code never imports from @kb-labs/sdk/studio; Studio page code never imports from @kb-labs/sdk's runtime hooks. Each subpath serves a different host.
See Plugins → Studio Pages for the Studio-side hooks.
What to read next
- SDK → Hooks — the full API reference for every hook listed above.
- SDK → Testing — patterns for testing handlers that use hooks.
- Plugins → Permissions — declaring which services your plugin can access.
- Plugins → Studio Pages — the hook catalog for Studio UI code.