@bernierllc/onboarding-config-core
v0.2.0
Published
Typed onboarding configuration schema, validation, builder, and system-prompt compiler for goal-directed conversational agents.
Readme
@bernierllc/onboarding-config-core
Typed onboarding configuration schema, validation, builder, and system-prompt compiler for goal-directed conversational agents.
Overview
This package provides the canonical data layer for conversational onboarding agents:
- Schema and validation — typed
OnboardingConfiginterfaces validated via@bernierllc/schema-validator - Fluent builder —
OnboardingConfigBuilderfor constructing configs programmatically - System-prompt compiler —
compileSystemPrompt()turns config + state into the prose an LLM receives - Returning-user context —
injectReturningUserContext()skips already-answered goals - Goal evaluator —
evaluateGoals()determines which goals are met and whether completion criteria are satisfied
Installation
npm install @bernierllc/onboarding-config-coreQuick start
import {
OnboardingConfigBuilder,
compileSystemPrompt,
evaluateGoals,
} from '@bernierllc/onboarding-config-core';
const config = new OnboardingConfigBuilder('contractor-onboarding')
.setPersona({
tone: 'warm-casual',
greeting: "Hey! Let's get {{appName}} set up in a few quick steps.",
askOneAtATime: true,
returningUserPrompt:
'The user has already completed: {{completedGoals}}. Skip those; pick up where they left off.',
})
.addPhase({ id: 'basics', title: 'Getting Started', guidance: 'Start with business name and type of work.', order: 1 })
.addPhase({ id: 'services', title: 'Services', guidance: 'Ask what services they offer, one at a time.', order: 2 })
.addPhase({ id: 'wrap-up', title: 'Wrapping Up', guidance: 'Confirm everything, mention next steps.', order: 3 })
.addGoal({
id: 'business-name',
description: 'Business name collected',
required: true,
phase: 'basics',
completionCheck: { type: 'field-present', field: 'businessName' },
})
.addGoal({
id: 'service-added',
description: 'At least one service added',
required: true,
phase: 'services',
completionCheck: { type: 'count-gte', field: 'services', threshold: 1 },
})
.addGoal({
id: 'logo-uploaded',
description: 'Logo uploaded',
required: false,
phase: 'basics',
completionCheck: { type: 'field-truthy', field: 'logoUrl' },
})
.addHandoff({ id: 'dashboard', type: 'route', label: 'Go to your dashboard', order: 1 })
.addHandoff({
id: 'calendar',
type: 'task',
label: 'Connect your calendar',
condition: 'state.calendarConnected !== true',
order: 2,
})
.setCompletionCriteria({ requireAllGoals: true })
.setFeatures({ enabledPlugins: [], pluginConfig: {} })
.build();Compiling a system prompt
compileSystemPrompt is a pure function — it has no side effects and can be called on every turn.
// First-time user
const systemPrompt = compileSystemPrompt(config, currentState, {
returningUser: false,
appName: 'Contractor Pro',
});
// Returning user — already answered some goals
const systemPrompt = compileSystemPrompt(config, currentState, {
returningUser: true,
appName: 'Contractor Pro',
});The compiler:
- Emits persona instructions (tone, single-question rule)
- Iterates phases in
order— emitting each guidance block with its goals - When
returningUser: true— annotates completed goals and injects thereturningUserPromptblock - Appends handoff instructions at the end
Evaluating goal progress
import { evaluateGoals } from '@bernierllc/onboarding-config-core';
const currentState = { businessName: 'Acme HVAC', services: ['AC repair'] };
const progress = evaluateGoals(config.goals, currentState, config.completionCriteria);
console.log(progress.isComplete); // false — logo not uploaded yet (optional, but all required met)
console.log(progress.requiredMet); // 2
console.log(progress.requiredTotal); // 2
console.log(progress.pendingRequired); // []Custom evaluators
Use type: 'custom' goals with an evaluator map for logic that cannot be expressed with field-present, field-truthy, or count-gte:
import { evaluateGoals, type CustomEvaluators } from '@bernierllc/onboarding-config-core';
const evaluators: CustomEvaluators = {
termsAccepted: (state) => state['termsVersion'] === '2025-01',
};
const progress = evaluateGoals(config.goals, currentState, config.completionCriteria, evaluators);Returning-user context injection
import { injectReturningUserContext } from '@bernierllc/onboarding-config-core';
const contextBlock = injectReturningUserContext(config, currentState);
// Returns a markdown block listing completed goals, or '' when nothing is done yet.Validating a raw config object
validate() is useful when configs arrive from external sources (database, API, user-supplied JSON):
import { validate } from '@bernierllc/onboarding-config-core';
const result = validate(rawConfig);
if (!result.valid) {
console.error(result.errors);
// [{ path: 'goals[0].completionCheck.threshold', message: '...', code: 'REQUIRED' }]
}
if (result.warnings.length > 0) {
console.warn(result.warnings);
// e.g. a goal references a phase id that does not exist
}Types reference
import type {
OnboardingConfig,
OnboardingPersona,
OnboardingPhase,
OnboardingGoal,
HandoffTarget,
CompletionCriteria,
FeatureConfig,
CompletionCheckDescriptor,
GoalProgress,
GoalStatus,
CustomEvaluators,
CompileOptions,
} from '@bernierllc/onboarding-config-core';CompletionCheckDescriptor.type values
| Type | Required fields | Description |
|------|----------------|-------------|
| field-present | field | Goal met when state[field] is not undefined |
| field-truthy | field | Goal met when state[field] is truthy |
| count-gte | field, threshold | Goal met when state[field] is an array with >= threshold items |
| custom | customKey | Goal met when the registered evaluator returns true |
Dot-notation is supported for field (e.g. "business.name").
Error handling
import {
OnboardingConfigError,
OnboardingConfigValidationError,
} from '@bernierllc/onboarding-config-core';
try {
const config = builder.build();
} catch (err) {
if (err instanceof OnboardingConfigValidationError) {
// err.code === 'ONBOARDING_CONFIG_VALIDATION_ERROR'
// err.context.errors — structured ValidationError[]
console.error('Config invalid:', err.context?.['errors']);
} else if (err instanceof OnboardingConfigError) {
// err.code — specific error code
// err.cause — underlying error (if any)
console.error('Config error:', err.code, err.cause);
}
}Integration flow
Caller (onboarding-agent-service)
|
validate(rawConfig) --> OnboardingConfig
|
compileSystemPrompt(config, state, { returningUser }) --> system prompt string
| (pass to LLM via ai-agent-runtime)
|
evaluateGoals(config.goals, latestState, config.completionCriteria) --> GoalProgress
| (compare to completionCriteria)
|
isComplete === true --> emit handoffs in HandoffTarget.order sequenceLicense
Copyright (c) 2025 Bernier LLC. See LICENSE for details.
