@bernierllc/onboarding-agent-suite
v0.1.0
Published
Drop-in bundle: one-call setup for config-driven conversational onboarding agents with plugin extensibility, streaming chat, goal tracking, and UI components.
Readme
@bernierllc/onboarding-agent-suite
Drop-in bundle for goal-directed conversational onboarding agents. One call wires config, plugins, AI runtime, streaming chat, Next.js route handlers, and UI components into a ready-to-embed system.
Installation
npm install @bernierllc/onboarding-agent-suiteQuick Start
import {
createOnboardingAgent,
OnboardingConfigBuilder,
OnboardingChat,
} from '@bernierllc/onboarding-agent-suite';
import { AnthropicProvider } from '@bernierllc/ai-provider-anthropic';
// 1. Build config
const config = new OnboardingConfigBuilder('contractor-onboarding', 'Contractor Onboarding')
.setPersona({
tone: 'warm-casual',
greeting: "Hey! Let's get {{businessName}} set up.",
askOneAtATime: true,
})
.addPhase({ id: 'setup', title: 'Getting Started', guidance: 'Ask name and business type.', order: 1 })
.addGoal({
id: 'business-name',
description: 'Business name collected',
required: true,
phase: 'setup',
completionCheck: { type: 'field-present', field: 'businessName' },
})
.setCompletionCriteria({ requireAllGoals: true })
.addHandoff({ id: 'dashboard', type: 'route', label: 'Go to dashboard', order: 1 })
.build();
// 2. Create agent (one call)
const { agent, mergedConfig, createChatHandler, createStatusHandler } = await createOnboardingAgent(config, {
aiProvider: new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY }),
stateReaders: {
async readState(sessionId) { return db.getOrgState(sessionId); },
},
handoffHandlers: {
async handle(target, state) {
if (target.type === 'route') router.push('/dashboard');
},
},
});
// 3. Wire Next.js route handlers
// app/api/onboarding/chat/route.ts:
export const POST = createChatHandler(agent, { auth: myAuthMiddleware });
// app/api/onboarding/status/route.ts:
export const GET = createStatusHandler(agent, { auth: myAuthMiddleware });
// 4. Render UI
// app/onboarding/page.tsx:
<OnboardingChat
chatEndpoint="/api/onboarding/chat"
statusEndpoint="/api/onboarding/status"
config={config}
panelDescriptors={mergedConfig.panels}
sessionId={orgId}
userId={userId}
/>API Reference
createOnboardingAgent(config, options)
Async factory that assembles and initialises the full onboarding system.
Parameters:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| config | OnboardingConfig | Yes | Base config from OnboardingConfigBuilder.build() |
| options.aiProvider | AIProvider | Yes | AI provider (Anthropic, OpenAI, Router) |
| options.stateReaders | StateReaders | Yes | Reads current session state for goal evaluation |
| options.handoffHandlers | HandoffHandlers | Yes | Executes post-completion handoff targets |
| options.plugins | Array<{plugin, config}> | No | Plugins to merge onto the base config |
| options.storeAdapter | ConversationStore | No | Conversation persistence (default: in-memory) |
| options.storage | StorageAdapter | No | File storage for attachment uploads |
| options.neverhub | boolean | No | NeverHub intent flag (documented; auto-detected by service) |
Returns: Promise<OnboardingAgentBundle>
interface OnboardingAgentBundle {
agent: OnboardingAgent; // initialised agent
mergedConfig: MergedConfig; // config after plugin merge
createChatHandler: typeof createOnboardingRouteHandler;
createStatusHandler: typeof createStatusRouteHandler;
createRatingHandler: typeof createRatingRouteHandler;
}Throws: OnboardingSuiteError (code: ONBOARDING_SUITE_INIT_ERROR) when plugin merge or agent initialization fails. Original error available via .cause.
OnboardingConfigBuilder
Re-exported from @bernierllc/onboarding-config-core. Fluent builder for OnboardingConfig.
const config = new OnboardingConfigBuilder('id', 'Display Name')
.setPersona({ tone: 'friendly-expert', greeting: '...', askOneAtATime: true })
.addPhase({ id: 'phase-1', title: 'Phase 1', guidance: '...', order: 1 })
.addGoal({
id: 'goal-1',
description: 'Collect info',
required: true,
phase: 'phase-1',
completionCheck: { type: 'field-present', field: 'fieldName' },
})
.setCompletionCriteria({ requireAllGoals: true })
.addHandoff({ id: 'handoff-1', type: 'route', label: 'Redirect', order: 1 })
.build();Completion check types: field-present, field-truthy, count-gte, custom
Route Handler Factories
All three factories are re-exported from @bernierllc/onboarding-agent-nextjs for use in Next.js App Router.
// app/api/onboarding/chat/route.ts
export const POST = createChatHandler(agent, {
auth: async (req) => ({ userId: session.userId, sessionId: session.orgId }),
});
// app/api/onboarding/status/route.ts
export const GET = createStatusHandler(agent, { auth });
// app/api/onboarding/rating/route.ts
export const POST = createRatingHandler(agent, { auth });UI Components
All components are tree-shakable re-exports from @bernierllc/onboarding-chat-ui.
| Export | Description |
|--------|-------------|
| OnboardingChat | Main container component (wraps all sub-components) |
| ChatPane | Message list + input area |
| SidePanel | Plugin panel slot container |
| ProgressChecklist | Goal completion checklist |
| CompletionCard | Shown when all required goals are met |
| RatingBar | Thumbs up/down rating |
| AttachmentDropzone | File upload dropzone |
| useOnboardingChat | Core hook for chat state and streaming |
| useGoalProgress | Goal status polling hook |
| usePanelRegistry | Plugin panel registration hook |
| useAutoGreeting | Auto-greeting on session start hook |
| OnboardingChatProvider | Context provider |
Plugin Author Guide
Plugins extend the onboarding system by contributing additional goals, phases, tools, UI panels, and handoff targets.
Implementing a Plugin
import type { OnboardingPlugin, PluginContext } from '@bernierllc/onboarding-agent-suite';
export const myPlugin: OnboardingPlugin = {
id: 'my-plugin', // stable unique id
name: 'My Plugin',
version: '1.0.0',
configSchema: {
type: 'object',
properties: {
apiKey: { type: 'string' },
},
required: ['apiKey'],
},
validateConfig(config): asserts config is { apiKey: string } {
if (typeof (config as { apiKey?: unknown }).apiKey !== 'string') {
throw new Error('apiKey is required');
}
},
contributeGoals(ctx: PluginContext) {
return [
{
id: 'my-plugin-goal',
description: 'Complete plugin setup',
required: true,
phase: 'setup',
completionCheck: { type: 'field-present', field: 'myPluginDone' },
},
];
},
contributePhases(ctx: PluginContext) {
return []; // Return additional phases if needed
},
contributeTools(ctx: PluginContext) {
return [
{
name: 'my_plugin_action',
description: 'Execute plugin-specific action',
parameters: {
type: 'object',
properties: { payload: { type: 'string' } },
required: ['payload'],
},
},
];
},
contributePanel(ctx: PluginContext) {
return {
slotId: 'my-plugin-panel',
label: 'My Plugin Panel',
triggerToolNames: ['my_plugin_action'],
};
},
contributeHandoffs(ctx: PluginContext, state: Record<string, unknown>) {
return []; // Return conditional handoff targets
},
};Registering a Plugin
const { agent, mergedConfig } = await createOnboardingAgent(config, {
aiProvider,
stateReaders,
handoffHandlers,
plugins: [
{ plugin: myPlugin, config: { apiKey: process.env.PLUGIN_API_KEY } },
],
});
// mergedConfig.panels contains the panel descriptors for all registered plugins
// Pass them to OnboardingChat:
<OnboardingChat
panelDescriptors={mergedConfig.panels}
panelComponents={{
'my-plugin-panel': MyPluginPanelComponent,
}}
/>Usage
See Quick Start above for the minimal setup. The pattern is always:
- Build an
OnboardingConfigviaOnboardingConfigBuilder - Call
createOnboardingAgent(config, options)with yourAIProvider,stateReaders, andhandoffHandlers - Wire the returned handler factories to Next.js App Router routes
- Render the
OnboardingChatcomponent with thechatEndpointandstatusEndpointprops
Integration Documentation
Logger integration: @bernierllc/logger is used internally by the service and Next.js handler packages for structured logging. All error paths and lifecycle events are logged automatically — no configuration required by consumers. Logger integration is implemented and active throughout the stack.
NeverHub integration: NeverHub is auto-detected at service startup. When a NeverHub instance is running, the onboarding service registers itself automatically. The neverhub option in createOnboardingAgent is an intent flag for documentation purposes — the service handles graceful degradation when NeverHub is unavailable. NeverHub integration supports graceful degradation: the suite initialises and operates fully whether or not NeverHub is present.
Integration status: logger integration — implemented; neverhub integration — implemented with graceful degradation.
MECE Architecture Note
This suite vs @bernierllc/flow-suite:
onboarding-agent-suite— Goal-directed LLM conversation: the model chooses the conversational path, phases are prose guidance, goals are evaluated against live state. Use for adaptive, conversational onboarding experiences.@bernierllc/flow-suite— Deterministic DAG flows: nodes and edges, the system executes a fixed decision tree. Use for structured multi-step automation with predictable paths.
These suites are mutually exclusive in responsibility. This suite only owns the assembly layer — it wires sub-packages together via createOnboardingAgent() and re-exports their public APIs. No business logic beyond assembly.
Error Handling
All errors are exported from the package for single-import convenience:
import {
OnboardingSuiteError, // factory-level errors
OnboardingAgentError, // service errors
OnboardingAgentInitError, // agent initialization failures
OnboardingHandoffError, // post-completion handoff failures
OnboardingConfigError, // config validation errors
OnboardingPluginError, // plugin registration errors
} from '@bernierllc/onboarding-agent-suite';
try {
const bundle = await createOnboardingAgent(config, options);
} catch (err) {
if (err instanceof OnboardingSuiteError) {
console.error('Suite init failed:', err.message, err.code);
console.error('Root cause:', err.cause);
}
}License
Copyright (c) 2025 Bernier LLC. Licensed under a limited-use license — see LICENSE file.
