docs: add BoxBrain wiki
420
API-Reference.md
Normal file
420
API-Reference.md
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
# API Reference
|
||||||
|
|
||||||
|
This page documents the current public surface exported from the BoxBrain package root.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export * from './adapters';
|
||||||
|
export * from './availability';
|
||||||
|
export * from './conversation';
|
||||||
|
export * from './grok';
|
||||||
|
export * from './memory';
|
||||||
|
export * from './persona';
|
||||||
|
export * from './schedule';
|
||||||
|
export * from './timing';
|
||||||
|
export * from './types';
|
||||||
|
```
|
||||||
|
|
||||||
|
## `persona`
|
||||||
|
|
||||||
|
### `initializePersona(db, input)`
|
||||||
|
|
||||||
|
Creates and persists a new persona runtime profile.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
initializePersona(db: IdentityDB, input: InitializePersonaInput): Promise<InitializedPersona>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important input fields**
|
||||||
|
|
||||||
|
- `displayName: string`
|
||||||
|
- `personality: string`
|
||||||
|
- `history?: string`
|
||||||
|
- `values?: string[]`
|
||||||
|
- `likes?: string[]`
|
||||||
|
- `dislikes?: string[]`
|
||||||
|
- `relationships?: PersonaRelationshipInput[]`
|
||||||
|
- `currentDate?: string`
|
||||||
|
- `structuredModel: StructuredModelAdapter`
|
||||||
|
- `imageModel?: ImageModelAdapter`
|
||||||
|
- `generateProfileImage?: boolean`
|
||||||
|
- `reuseExistingSpace?: boolean`
|
||||||
|
|
||||||
|
**Behavior**
|
||||||
|
|
||||||
|
- generates a biography through `structuredModel`
|
||||||
|
- extracts fact drafts through `structuredModel`
|
||||||
|
- writes biography facts into the persona space
|
||||||
|
- optionally generates a profile image and writes a profile-image fact
|
||||||
|
- creates a collision-resistant persona ID if one is not provided
|
||||||
|
|
||||||
|
**Returns**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface InitializedPersona {
|
||||||
|
id: string;
|
||||||
|
spaceName: string;
|
||||||
|
displayName: string;
|
||||||
|
biography: string;
|
||||||
|
profileImageUrl?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `schedule`
|
||||||
|
|
||||||
|
### `generateSchedule(db, input)`
|
||||||
|
|
||||||
|
Generates and persists schedule events, then derives schedule-backed availability entries.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
generateSchedule(
|
||||||
|
db: IdentityDB,
|
||||||
|
input: GenerateScheduleInput,
|
||||||
|
): Promise<{ events: BoxBrainScheduleEvent[]; availabilityEntries: BoxBrainAvailabilityEntry[] }>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important input fields**
|
||||||
|
|
||||||
|
- `spaceName: string`
|
||||||
|
- `displayName?: string`
|
||||||
|
- `currentDate: string`
|
||||||
|
- `scope: 'day' | 'week' | 'month'`
|
||||||
|
- `timezone?: string`
|
||||||
|
- `structuredModel: StructuredModelAdapter`
|
||||||
|
- `specialDateProvider?: SpecialDateProvider`
|
||||||
|
|
||||||
|
### `listScheduleEvents(db, input)`
|
||||||
|
|
||||||
|
Lists persisted schedule events, excluding ones hidden by deletion markers.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
listScheduleEvents(db: IdentityDB, input: ListScheduleEventsInput): Promise<BoxBrainScheduleEvent[]>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Filters**
|
||||||
|
|
||||||
|
- `spaceName`
|
||||||
|
- optional `from`
|
||||||
|
- optional `until`
|
||||||
|
|
||||||
|
### `pruneExpiredSchedule(db, input)`
|
||||||
|
|
||||||
|
Marks schedule events as deleted when they ended before `referenceTime` minus an optional grace window.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pruneExpiredSchedule(db: IdentityDB, input: PruneExpiredScheduleInput): Promise<SchedulePruneResult>
|
||||||
|
```
|
||||||
|
|
||||||
|
### `pruneScheduleBefore(db, input)`
|
||||||
|
|
||||||
|
Marks schedule events as deleted when they start before a cutoff timestamp.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pruneScheduleBefore(db: IdentityDB, input: PruneScheduleBeforeInput): Promise<SchedulePruneResult>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common types**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type BoxBrainScheduleScope = 'day' | 'week' | 'month';
|
||||||
|
type BoxBrainScheduleEventKind = 'routine' | 'special';
|
||||||
|
|
||||||
|
interface BoxBrainScheduleEvent {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
startAt: string;
|
||||||
|
endAt: string;
|
||||||
|
availabilityMode: BoxBrainAvailabilityMode;
|
||||||
|
availabilityReason?: string;
|
||||||
|
kind: BoxBrainScheduleEventKind;
|
||||||
|
topics: BoxBrainTopicDraft[];
|
||||||
|
metadata?: JsonValue | null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `availability`
|
||||||
|
|
||||||
|
### `setAvailabilityStatus(db, input)`
|
||||||
|
|
||||||
|
Persists an explicit availability entry.
|
||||||
|
|
||||||
|
**Signature**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
setAvailabilityStatus(db: IdentityDB, input: SetAvailabilityStatusInput): Promise<BoxBrainAvailabilityEntry>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important input fields**
|
||||||
|
|
||||||
|
- `spaceName: string`
|
||||||
|
- `mode: 'online' | 'do_not_disturb' | 'offline'`
|
||||||
|
- `reason?: string`
|
||||||
|
- `effectiveFrom: string`
|
||||||
|
- `until?: string`
|
||||||
|
- `sourceType?: 'schedule' | 'manual' | 'tool'`
|
||||||
|
- `eventId?: string`
|
||||||
|
- `metadata?: JsonValue | null`
|
||||||
|
|
||||||
|
### `listAvailabilityEntries(db, input)`
|
||||||
|
|
||||||
|
Lists explicit availability entries in chronological order.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
listAvailabilityEntries(db: IdentityDB, input: ListAvailabilityEntriesInput): Promise<BoxBrainAvailabilityEntry[]>
|
||||||
|
```
|
||||||
|
|
||||||
|
### `getAvailabilitySnapshot(db, input)`
|
||||||
|
|
||||||
|
Returns the active availability state at a timestamp plus the next transition.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
getAvailabilitySnapshot(db: IdentityDB, input: GetAvailabilitySnapshotInput): Promise<BoxBrainAvailabilitySnapshot>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important types**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type BoxBrainAvailabilityMode = 'online' | 'do_not_disturb' | 'offline';
|
||||||
|
type BoxBrainAvailabilitySourceType = 'default' | 'schedule' | 'manual' | 'tool';
|
||||||
|
|
||||||
|
interface BoxBrainAvailabilityEntry {
|
||||||
|
id: string;
|
||||||
|
mode: BoxBrainAvailabilityMode;
|
||||||
|
reason?: string;
|
||||||
|
effectiveFrom: string;
|
||||||
|
until?: string;
|
||||||
|
sourceType: BoxBrainAvailabilitySourceType;
|
||||||
|
eventId?: string;
|
||||||
|
createdAt?: string;
|
||||||
|
metadata?: JsonValue | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BoxBrainAvailabilitySnapshot {
|
||||||
|
current: BoxBrainAvailabilityEntry;
|
||||||
|
next: BoxBrainAvailabilityEntry | null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `conversation`
|
||||||
|
|
||||||
|
### `replyToConversation(db, input)`
|
||||||
|
|
||||||
|
Persists an inbound message and generates a DM-style response turn.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
replyToConversation(db: IdentityDB, input: ReplyToConversationInput): Promise<ConversationTurnResult>
|
||||||
|
```
|
||||||
|
|
||||||
|
### `startConversation(db, input)`
|
||||||
|
|
||||||
|
Generates a proactive outbound opening turn with no inbound user message.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
startConversation(db: IdentityDB, input: StartConversationInput): Promise<ConversationTurnResult>
|
||||||
|
```
|
||||||
|
|
||||||
|
### `listConversationEntries(db, input)`
|
||||||
|
|
||||||
|
Lists stored conversation entries for a persona, optionally filtered by counterpart and time range.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
listConversationEntries(db: IdentityDB, input: ListConversationEntriesInput): Promise<BoxBrainConversationEntry[]>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Required model roles for turn generation**
|
||||||
|
|
||||||
|
Both `replyToConversation` and `startConversation` require:
|
||||||
|
|
||||||
|
- `mandatoryMemoryModel: StructuredModelAdapter`
|
||||||
|
- `contextualMemoryModel: StructuredModelAdapter`
|
||||||
|
- `responseModel: StructuredModelAdapter`
|
||||||
|
|
||||||
|
**Turn result**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ConversationTurnResult {
|
||||||
|
blocked: boolean;
|
||||||
|
blockedReason?: string;
|
||||||
|
blockedUntil?: string;
|
||||||
|
messages: BoxBrainMessage[];
|
||||||
|
usedMemories: BoxBrainMemoryReference[];
|
||||||
|
toolCallsExecuted: ConversationToolCall[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Message type**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface BoxBrainMessage {
|
||||||
|
text: string;
|
||||||
|
typingDelaySeconds: number;
|
||||||
|
replyDelaySeconds: number;
|
||||||
|
totalDelaySeconds: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `timing`
|
||||||
|
|
||||||
|
### Availability constants
|
||||||
|
|
||||||
|
```ts
|
||||||
|
ONLINE_AVAILABILITY
|
||||||
|
DND_AVAILABILITY
|
||||||
|
OFFLINE_AVAILABILITY
|
||||||
|
```
|
||||||
|
|
||||||
|
### `createTypingDelay(message, options?)`
|
||||||
|
|
||||||
|
Computes a per-message typing delay based on message length.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
createTypingDelay(message: string, options?: TypingDelayOptions): number
|
||||||
|
```
|
||||||
|
|
||||||
|
Default per-character range:
|
||||||
|
|
||||||
|
- minimum: `0.05` seconds
|
||||||
|
- maximum: `0.08` seconds
|
||||||
|
|
||||||
|
### `createReplyDelay(availability, options)`
|
||||||
|
|
||||||
|
Computes the initial reply delay for the first message in an exchange.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
createReplyDelay(availability: BoxBrainAvailability, options: ReplyDelayOptions): number | null
|
||||||
|
```
|
||||||
|
|
||||||
|
Behavior summary:
|
||||||
|
|
||||||
|
- `offline` -> `null`
|
||||||
|
- not first reply in exchange -> `0`
|
||||||
|
- `do_not_disturb` -> probabilistic reply or `null`
|
||||||
|
- `online` -> short randomized delay
|
||||||
|
|
||||||
|
## `adapters`
|
||||||
|
|
||||||
|
### `TextModelAdapter`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface TextModelAdapter {
|
||||||
|
provider: string;
|
||||||
|
model: string;
|
||||||
|
generateText(request: TextGenerationRequest): Promise<string>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `StructuredModelAdapter`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface StructuredModelAdapter<TSchema = unknown> {
|
||||||
|
provider: string;
|
||||||
|
model: string;
|
||||||
|
generateObject<TOutput>(request: StructuredGenerationRequest<TSchema>): Promise<TOutput>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `ImageModelAdapter`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ImageModelAdapter {
|
||||||
|
provider: string;
|
||||||
|
model: string;
|
||||||
|
generateImage(request: ImageGenerationRequest): Promise<ImageGenerationResult>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `SpecialDateProvider`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface SpecialDateProvider {
|
||||||
|
listSpecialDates(request: SpecialDateRequest): Promise<SpecialDateContext[]>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `grok`
|
||||||
|
|
||||||
|
### `createGrokTextModelAdapter(options)`
|
||||||
|
|
||||||
|
Creates a `TextModelAdapter` that uses xAI chat completions.
|
||||||
|
|
||||||
|
### `createGrokStructuredModelAdapter(options)`
|
||||||
|
|
||||||
|
Creates a `StructuredModelAdapter` that uses xAI chat completions with `response_format` configured as JSON object or JSON schema.
|
||||||
|
|
||||||
|
### `createGrokImageModelAdapter(options)`
|
||||||
|
|
||||||
|
Creates an `ImageModelAdapter` that uses xAI image generation.
|
||||||
|
|
||||||
|
### `createGrokAdapters(options)`
|
||||||
|
|
||||||
|
Convenience bundle creator.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
createGrokAdapters(options: GrokAdapterBundleOptions): {
|
||||||
|
text: TextModelAdapter;
|
||||||
|
structured: StructuredModelAdapter;
|
||||||
|
image: ImageModelAdapter;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `memory`
|
||||||
|
|
||||||
|
### `persistFactDrafts(db, input)`
|
||||||
|
|
||||||
|
Low-level helper for writing BoxBrain fact drafts into IdentityDB.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
persistFactDrafts(db: IdentityDB, input: PersistFactDraftsInput): Promise<Fact[]>
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful when extending BoxBrain with your own runtime modules.
|
||||||
|
|
||||||
|
## `types`
|
||||||
|
|
||||||
|
Selected important domain types:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type BoxBrainFactDomain =
|
||||||
|
| 'persona.biography'
|
||||||
|
| 'persona.profile_image'
|
||||||
|
| 'persona.schedule'
|
||||||
|
| 'persona.schedule.deleted'
|
||||||
|
| 'persona.availability'
|
||||||
|
| 'persona.conversation'
|
||||||
|
| 'persona.relationship'
|
||||||
|
| (string & {});
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface BoxBrainPersonaProfile {
|
||||||
|
id: string;
|
||||||
|
spaceName: string;
|
||||||
|
displayName: string;
|
||||||
|
profileImageUrl?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface BoxBrainConversationEntry {
|
||||||
|
id: string;
|
||||||
|
turnId: string;
|
||||||
|
direction: 'inbound' | 'outbound';
|
||||||
|
text: string;
|
||||||
|
occurredAt: string;
|
||||||
|
createdAt?: string;
|
||||||
|
counterpartId: string;
|
||||||
|
counterpartDisplayName?: string;
|
||||||
|
proactive: boolean;
|
||||||
|
metadata?: JsonValue | null;
|
||||||
|
}
|
||||||
|
```
|
||||||
273
Getting-Started.md
Normal file
273
Getting-Started.md
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
# Getting Started
|
||||||
|
|
||||||
|
## Current setup assumptions
|
||||||
|
|
||||||
|
At the current stage, BoxBrain is a local TypeScript/Bun framework that depends on a sibling checkout of IdentityDB.
|
||||||
|
|
||||||
|
The package configuration currently points to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"identitydb": "file:../IdentityDB"
|
||||||
|
```
|
||||||
|
|
||||||
|
So a practical local setup looks like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.psw.kr/p-sw/BoxBrain.git
|
||||||
|
git clone https://git.psw.kr/p-sw/IdentityDB.git
|
||||||
|
```
|
||||||
|
|
||||||
|
with both repositories living next to each other.
|
||||||
|
|
||||||
|
## Install and verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd BoxBrain
|
||||||
|
bun install
|
||||||
|
bun run test
|
||||||
|
bun run check
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Minimal dependencies
|
||||||
|
|
||||||
|
You need:
|
||||||
|
|
||||||
|
- Node.js 20+
|
||||||
|
- Bun
|
||||||
|
- IdentityDB available at `../IdentityDB`
|
||||||
|
|
||||||
|
## Create an IdentityDB instance
|
||||||
|
|
||||||
|
BoxBrain uses IdentityDB as the persistence layer.
|
||||||
|
|
||||||
|
A minimal in-memory setup:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { IdentityDB } from 'identitydb';
|
||||||
|
|
||||||
|
const db = await IdentityDB.connect({
|
||||||
|
client: 'sqlite',
|
||||||
|
filename: ':memory:',
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.initialize();
|
||||||
|
```
|
||||||
|
|
||||||
|
For a persistent local database, replace `':memory:'` with a file path.
|
||||||
|
|
||||||
|
## Create model adapters
|
||||||
|
|
||||||
|
BoxBrain runtime code expects provider adapters instead of hard-coding a specific vendor inside the core functions.
|
||||||
|
|
||||||
|
### Provider-agnostic contracts
|
||||||
|
|
||||||
|
The package exports these adapter interfaces:
|
||||||
|
|
||||||
|
- `TextModelAdapter`
|
||||||
|
- `StructuredModelAdapter`
|
||||||
|
- `ImageModelAdapter`
|
||||||
|
- `SpecialDateProvider`
|
||||||
|
|
||||||
|
### Quick start with Grok
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createGrokAdapters } from 'boxbrain';
|
||||||
|
|
||||||
|
const grok = createGrokAdapters({
|
||||||
|
apiKey: process.env.XAI_API_KEY!,
|
||||||
|
textModel: 'grok-4.3-mini',
|
||||||
|
structuredModel: 'grok-4.3',
|
||||||
|
imageModel: 'grok-imagine-image-quality',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Initialize a persona
|
||||||
|
|
||||||
|
This is the first major BoxBrain workflow.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { IdentityDB } from 'identitydb';
|
||||||
|
import { createGrokAdapters, initializePersona } from 'boxbrain';
|
||||||
|
|
||||||
|
const db = await IdentityDB.connect({ client: 'sqlite', filename: ':memory:' });
|
||||||
|
await db.initialize();
|
||||||
|
|
||||||
|
const grok = createGrokAdapters({
|
||||||
|
apiKey: process.env.XAI_API_KEY!,
|
||||||
|
textModel: 'grok-4.3-mini',
|
||||||
|
structuredModel: 'grok-4.3',
|
||||||
|
imageModel: 'grok-imagine-image-quality',
|
||||||
|
});
|
||||||
|
|
||||||
|
const persona = await initializePersona(db, {
|
||||||
|
displayName: 'Mina',
|
||||||
|
personality: 'Thoughtful, witty, introverted, and emotionally observant.',
|
||||||
|
history: 'Raised in Busan, later moved to Seoul to work in product design.',
|
||||||
|
values: ['loyalty', 'self-respect', 'quiet consistency'],
|
||||||
|
likes: ['late-night walks', 'indie music', 'quiet cafés'],
|
||||||
|
dislikes: ['performative networking', 'loud restaurants'],
|
||||||
|
relationships: [
|
||||||
|
{
|
||||||
|
name: 'Jisoo',
|
||||||
|
relationship: 'older brother',
|
||||||
|
description: 'protective but teasing',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
currentDate: '2026-05-11',
|
||||||
|
structuredModel: grok.structured,
|
||||||
|
imageModel: grok.image,
|
||||||
|
generateProfileImage: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(persona);
|
||||||
|
```
|
||||||
|
|
||||||
|
### What initialization does
|
||||||
|
|
||||||
|
- generates a detailed biography
|
||||||
|
- extracts IdentityDB-ready facts
|
||||||
|
- creates or reuses an IdentityDB space (depending on options)
|
||||||
|
- optionally generates a profile image
|
||||||
|
- persists biography and image facts
|
||||||
|
|
||||||
|
## Generate a schedule
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { generateSchedule } from 'boxbrain';
|
||||||
|
|
||||||
|
const schedule = await generateSchedule(db, {
|
||||||
|
spaceName: persona.spaceName,
|
||||||
|
displayName: persona.displayName,
|
||||||
|
currentDate: '2026-05-11',
|
||||||
|
scope: 'week',
|
||||||
|
timezone: 'Asia/Seoul',
|
||||||
|
structuredModel: grok.structured,
|
||||||
|
specialDateProvider: {
|
||||||
|
async listSpecialDates() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
date: '2026-05-15',
|
||||||
|
title: 'Teacher’s Day',
|
||||||
|
description: 'A recurring Korean commemorative day.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(schedule.events);
|
||||||
|
console.log(schedule.availabilityEntries);
|
||||||
|
```
|
||||||
|
|
||||||
|
### What schedule generation does
|
||||||
|
|
||||||
|
- reads persona facts and recent schedule continuity
|
||||||
|
- optionally loads external special-date context
|
||||||
|
- generates realistic events for a day/week/month window
|
||||||
|
- stores schedule facts in IdentityDB
|
||||||
|
- automatically emits schedule-derived availability entries
|
||||||
|
|
||||||
|
## Read or override availability
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { getAvailabilitySnapshot, setAvailabilityStatus } from 'boxbrain';
|
||||||
|
|
||||||
|
await setAvailabilityStatus(db, {
|
||||||
|
spaceName: persona.spaceName,
|
||||||
|
mode: 'do_not_disturb',
|
||||||
|
reason: 'studying for an exam',
|
||||||
|
effectiveFrom: '2026-05-11T19:00:00.000Z',
|
||||||
|
until: '2026-05-11T22:00:00.000Z',
|
||||||
|
sourceType: 'manual',
|
||||||
|
});
|
||||||
|
|
||||||
|
const snapshot = await getAvailabilitySnapshot(db, {
|
||||||
|
spaceName: persona.spaceName,
|
||||||
|
at: '2026-05-11T20:00:00.000Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(snapshot.current);
|
||||||
|
console.log(snapshot.next);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run a conversation turn
|
||||||
|
|
||||||
|
BoxBrain separates memory retrieval and response planning into three structured model roles:
|
||||||
|
|
||||||
|
- `mandatoryMemoryModel`
|
||||||
|
- `contextualMemoryModel`
|
||||||
|
- `responseModel`
|
||||||
|
|
||||||
|
You can point all three at the same underlying model if you want.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { replyToConversation, startConversation } from 'boxbrain';
|
||||||
|
|
||||||
|
const proactive = await startConversation(db, {
|
||||||
|
spaceName: persona.spaceName,
|
||||||
|
counterpartId: 'user:shinwoo',
|
||||||
|
counterpartDisplayName: 'Shinwoo',
|
||||||
|
currentTime: '2026-05-11T12:00:00.000Z',
|
||||||
|
mandatoryMemoryModel: grok.structured,
|
||||||
|
contextualMemoryModel: grok.structured,
|
||||||
|
responseModel: grok.structured,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(proactive.messages);
|
||||||
|
|
||||||
|
const reply = await replyToConversation(db, {
|
||||||
|
spaceName: persona.spaceName,
|
||||||
|
counterpartId: 'user:shinwoo',
|
||||||
|
counterpartDisplayName: 'Shinwoo',
|
||||||
|
currentTime: '2026-05-11T12:05:00.000Z',
|
||||||
|
message: 'Are you free tonight?',
|
||||||
|
mandatoryMemoryModel: grok.structured,
|
||||||
|
contextualMemoryModel: grok.structured,
|
||||||
|
responseModel: grok.structured,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(reply.blocked);
|
||||||
|
console.log(reply.messages);
|
||||||
|
console.log(reply.usedMemories);
|
||||||
|
console.log(reply.toolCallsExecuted);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Timing helpers
|
||||||
|
|
||||||
|
If you want to inspect or reuse BoxBrain’s timing rules directly:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import {
|
||||||
|
ONLINE_AVAILABILITY,
|
||||||
|
createReplyDelay,
|
||||||
|
createTypingDelay,
|
||||||
|
} from 'boxbrain';
|
||||||
|
|
||||||
|
const replyDelay = createReplyDelay(ONLINE_AVAILABILITY, {
|
||||||
|
isFirstReplyInExchange: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const typingDelay = createTypingDelay('지금 뭐해?', {
|
||||||
|
minSecondsPerCharacter: 0.05,
|
||||||
|
maxSecondsPerCharacter: 0.08,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
### 1. BoxBrain is currently a library, not an HTTP service
|
||||||
|
|
||||||
|
You call the exported TypeScript APIs directly.
|
||||||
|
|
||||||
|
### 2. IdentityDB is a required runtime dependency
|
||||||
|
|
||||||
|
The framework assumes IdentityDB is available and initialized.
|
||||||
|
|
||||||
|
### 3. Current local development uses a sibling checkout
|
||||||
|
|
||||||
|
Because the package currently depends on `file:../IdentityDB`, a standalone `npm install boxbrain` style flow is not the current development path yet.
|
||||||
|
|
||||||
|
### 4. `better-sqlite3` native rebuilds may be needed in local dev
|
||||||
|
|
||||||
|
If you hit a native module mismatch through the local IdentityDB dependency, rebuilding `better-sqlite3` from the IdentityDB workspace may be required.
|
||||||
86
Home.md
86
Home.md
@@ -0,0 +1,86 @@
|
|||||||
|
# BoxBrain
|
||||||
|
|
||||||
|
BoxBrain is an IdentityDB-backed TypeScript framework for building **synthetic personas that feel like real DM contacts**.
|
||||||
|
|
||||||
|
It is not a finished chatbot product. It is a **framework for persona runtime design**: a reusable harness for turning long-term memory, schedules, availability, and multi-step LLM orchestration into believable human-like messaging behavior.
|
||||||
|
|
||||||
|
## Why BoxBrain exists
|
||||||
|
|
||||||
|
Most LLM chat systems are optimized for correctness, utility, or task completion. BoxBrain exists for a different goal:
|
||||||
|
|
||||||
|
- make a persona feel like a **specific person**, not a generic assistant
|
||||||
|
- preserve continuity through **IdentityDB facts** instead of a short rolling prompt only
|
||||||
|
- let that persona have a **life outside the chat**
|
||||||
|
- make conversation timing and availability feel **human**, not instant and mechanical
|
||||||
|
- keep the whole system **framework-first**, so applications can compose their own transports and product UX on top
|
||||||
|
|
||||||
|
In short: BoxBrain is a harness for building personas that have memory, history, schedules, contactability, and DM-style messaging behavior.
|
||||||
|
|
||||||
|
## What the framework currently provides
|
||||||
|
|
||||||
|
- provider-agnostic adapter contracts for text, structured-output, image, and special-date retrieval
|
||||||
|
- a ready-made xAI Grok adapter set for text, structured-output, and image generation
|
||||||
|
- one IdentityDB **space** per persona as the primary isolation boundary
|
||||||
|
- persona initialization from personality, history, values, likes, dislikes, and relationships
|
||||||
|
- LLM-generated biography creation followed by fact extraction into IdentityDB
|
||||||
|
- optional profile image generation during persona initialization
|
||||||
|
- realistic schedule generation for day, week, and month scopes
|
||||||
|
- schedule persistence, listing, and pruning
|
||||||
|
- availability state persistence with schedule, manual, and tool-driven overrides
|
||||||
|
- availability snapshots with current and next transition resolution
|
||||||
|
- DM-style conversation orchestration for inbound replies and proactive openings
|
||||||
|
- two-stage memory retrieval for conversation turns: mandatory memories first, contextual memories second
|
||||||
|
- human-like first-reply delay and per-message typing delay helpers
|
||||||
|
- refusal / farewell flows that can trigger an availability-changing tool call
|
||||||
|
|
||||||
|
## What is still planned
|
||||||
|
|
||||||
|
These are **not** implemented as part of the core library yet:
|
||||||
|
|
||||||
|
- HTTP/RPC wrappers around the runtime APIs
|
||||||
|
- additional ready-made vendor adapter packages beyond Grok
|
||||||
|
- production-focused runtime/persistence integrations beyond the in-process core library
|
||||||
|
|
||||||
|
## Recommended reading order
|
||||||
|
|
||||||
|
1. [Purpose and Architecture](Purpose-and-Architecture)
|
||||||
|
2. [Getting Started](Getting-Started)
|
||||||
|
3. [API Reference](API-Reference)
|
||||||
|
4. [xAI Grok Adapter](xAI-Grok-Adapter)
|
||||||
|
|
||||||
|
## BoxBrain mental model
|
||||||
|
|
||||||
|
A BoxBrain persona works like this:
|
||||||
|
|
||||||
|
1. **Initialize a persona** from structured traits and history.
|
||||||
|
2. Generate a detailed biography.
|
||||||
|
3. Extract biography facts and store them in the persona's IdentityDB space.
|
||||||
|
4. Generate schedules anchored to time and external special dates.
|
||||||
|
5. Derive availability from schedule or explicit overrides.
|
||||||
|
6. Run conversations by retrieving mandatory/contextual memories, generating a turn plan, and emitting DM-style messages with delays.
|
||||||
|
|
||||||
|
## Current code surface
|
||||||
|
|
||||||
|
The package root currently exports:
|
||||||
|
|
||||||
|
- `./adapters`
|
||||||
|
- `./availability`
|
||||||
|
- `./conversation`
|
||||||
|
- `./grok`
|
||||||
|
- `./memory`
|
||||||
|
- `./persona`
|
||||||
|
- `./schedule`
|
||||||
|
- `./timing`
|
||||||
|
- `./types`
|
||||||
|
|
||||||
|
## Development status
|
||||||
|
|
||||||
|
Repository status at the time of writing:
|
||||||
|
|
||||||
|
- package/runtime: **Bun**
|
||||||
|
- language: **TypeScript**
|
||||||
|
- tests: **Vitest**
|
||||||
|
- build: **tsup**
|
||||||
|
- storage/memory dependency: **IdentityDB**
|
||||||
|
|
||||||
|
See [Getting Started](Getting-Started) for a working local setup pattern.
|
||||||
|
|||||||
226
Purpose-and-Architecture.md
Normal file
226
Purpose-and-Architecture.md
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
# Purpose and Architecture
|
||||||
|
|
||||||
|
## Existence and design intent
|
||||||
|
|
||||||
|
BoxBrain exists to answer a specific question:
|
||||||
|
|
||||||
|
> What does an LLM runtime need in order to feel less like a chatbot and more like an actual person in a DM thread?
|
||||||
|
|
||||||
|
Its answer is: memory alone is not enough.
|
||||||
|
|
||||||
|
A believable persona also needs:
|
||||||
|
|
||||||
|
- a persistent personal history
|
||||||
|
- preferences and values that survive across turns
|
||||||
|
- relationships with named people
|
||||||
|
- a time-aware schedule
|
||||||
|
- changing availability
|
||||||
|
- conversation-specific memory retrieval
|
||||||
|
- message timing that feels human
|
||||||
|
|
||||||
|
This is why BoxBrain is not just a prompt template or a single conversation function. It is a small framework that coordinates multiple model calls and stores their outcomes in IdentityDB.
|
||||||
|
|
||||||
|
## Core principles
|
||||||
|
|
||||||
|
### 1. Framework-first, not app-first
|
||||||
|
|
||||||
|
BoxBrain intentionally stops at the runtime/library layer.
|
||||||
|
|
||||||
|
It gives you primitives for:
|
||||||
|
|
||||||
|
- persona initialization
|
||||||
|
- schedule generation
|
||||||
|
- availability control
|
||||||
|
- conversation orchestration
|
||||||
|
- provider adapters
|
||||||
|
|
||||||
|
It does **not** currently ship with its own HTTP server, frontend, or transport product.
|
||||||
|
|
||||||
|
### 2. IdentityDB space = one persona
|
||||||
|
|
||||||
|
Each persona is isolated into its own IdentityDB space.
|
||||||
|
|
||||||
|
That means:
|
||||||
|
|
||||||
|
- biography facts belong to one persona space
|
||||||
|
- schedule facts belong to that same persona space
|
||||||
|
- availability facts belong to that same persona space
|
||||||
|
- conversation history belongs to that same persona space
|
||||||
|
|
||||||
|
This is the main boundary that prevents one persona's memories from bleeding into another persona.
|
||||||
|
|
||||||
|
### 3. Structured generation before persistence
|
||||||
|
|
||||||
|
BoxBrain does not trust raw free-form LLM output for important state transitions.
|
||||||
|
|
||||||
|
It prefers:
|
||||||
|
|
||||||
|
- structured biography generation
|
||||||
|
- structured fact extraction
|
||||||
|
- structured schedule generation
|
||||||
|
- structured memory selection
|
||||||
|
- structured conversation turn planning
|
||||||
|
|
||||||
|
This makes the runtime more deterministic, testable, and safer to persist into IdentityDB.
|
||||||
|
|
||||||
|
### 4. A persona should have a life outside the chat
|
||||||
|
|
||||||
|
A BoxBrain persona is not assumed to be available all the time.
|
||||||
|
|
||||||
|
Instead:
|
||||||
|
|
||||||
|
- schedules create time-bound events
|
||||||
|
- schedule events can create availability windows
|
||||||
|
- manual or tool-driven overrides can supersede schedule-derived states
|
||||||
|
- conversations can be blocked or delayed depending on availability
|
||||||
|
|
||||||
|
This is the mechanism that makes a persona feel like it has its own life rhythm.
|
||||||
|
|
||||||
|
## Runtime architecture
|
||||||
|
|
||||||
|
## 1. Persona initialization
|
||||||
|
|
||||||
|
Initialization takes explicit seed attributes such as:
|
||||||
|
|
||||||
|
- display name
|
||||||
|
- personality
|
||||||
|
- history
|
||||||
|
- values
|
||||||
|
- likes / dislikes
|
||||||
|
- relationships
|
||||||
|
|
||||||
|
Then BoxBrain:
|
||||||
|
|
||||||
|
1. asks a structured model to generate a detailed biography
|
||||||
|
2. asks a structured model to split that biography into IdentityDB-ready facts
|
||||||
|
3. persists those facts into the persona's IdentityDB space
|
||||||
|
4. optionally generates a profile image through an image adapter
|
||||||
|
|
||||||
|
The result is a persona profile with a stable `id`, `spaceName`, `displayName`, biography, and optional profile image URL.
|
||||||
|
|
||||||
|
## 2. Schedule runtime
|
||||||
|
|
||||||
|
Schedules are generated for a `day`, `week`, or `month` scope.
|
||||||
|
|
||||||
|
The schedule generator combines:
|
||||||
|
|
||||||
|
- existing persona facts
|
||||||
|
- recent schedule continuity
|
||||||
|
- optional external special-date context
|
||||||
|
- the anchor date and optional timezone
|
||||||
|
|
||||||
|
Generated events are stored as `persona.schedule` facts.
|
||||||
|
|
||||||
|
Each event includes:
|
||||||
|
|
||||||
|
- title
|
||||||
|
- optional description
|
||||||
|
- `startAt`
|
||||||
|
- `endAt`
|
||||||
|
- `availabilityMode`
|
||||||
|
- optional `availabilityReason`
|
||||||
|
- `kind` (`routine` or `special`)
|
||||||
|
- topics / metadata
|
||||||
|
|
||||||
|
Schedule pruning does not mutate old facts directly. Instead, BoxBrain writes `persona.schedule.deleted` marker facts so the runtime can treat old events as deleted while keeping an append-only fact history.
|
||||||
|
|
||||||
|
## 3. Availability runtime
|
||||||
|
|
||||||
|
Availability facts represent whether the persona is:
|
||||||
|
|
||||||
|
- `online`
|
||||||
|
- `do_not_disturb`
|
||||||
|
- `offline`
|
||||||
|
|
||||||
|
Availability can come from different sources:
|
||||||
|
|
||||||
|
- `schedule`
|
||||||
|
- `manual`
|
||||||
|
- `tool`
|
||||||
|
- `default`
|
||||||
|
|
||||||
|
`default` is implicit and resolves to online when no explicit entry applies.
|
||||||
|
|
||||||
|
A snapshot query returns:
|
||||||
|
|
||||||
|
- the **current** active availability state at a timestamp
|
||||||
|
- the **next** availability transition, if one exists
|
||||||
|
|
||||||
|
This gives upstream applications enough information to render presence and decide whether to send or defer a conversation turn.
|
||||||
|
|
||||||
|
## 4. Conversation runtime
|
||||||
|
|
||||||
|
A BoxBrain conversation turn is intentionally multi-stage.
|
||||||
|
|
||||||
|
### Stage A: availability check
|
||||||
|
|
||||||
|
Before generating a response, BoxBrain checks the persona's current availability snapshot.
|
||||||
|
|
||||||
|
If the persona is offline, the turn is blocked.
|
||||||
|
|
||||||
|
### Stage B: mandatory memory retrieval
|
||||||
|
|
||||||
|
A structured retrieval model chooses memories that must always be considered, especially:
|
||||||
|
|
||||||
|
- yesterday / today / tomorrow schedule context
|
||||||
|
- recent conversation context
|
||||||
|
- stable persona or counterpart facts
|
||||||
|
|
||||||
|
### Stage C: contextual memory retrieval
|
||||||
|
|
||||||
|
A second structured retrieval model chooses additional relevant memories based on the specific user message or proactive context.
|
||||||
|
|
||||||
|
### Stage D: turn planning
|
||||||
|
|
||||||
|
A response model generates a structured turn plan:
|
||||||
|
|
||||||
|
- `mode: 'reply' | 'refuse'`
|
||||||
|
- `messages: string[]`
|
||||||
|
- optional `toolCalls`
|
||||||
|
|
||||||
|
### Stage E: human-like timing
|
||||||
|
|
||||||
|
BoxBrain calculates:
|
||||||
|
|
||||||
|
- first-reply delay based on availability mode and whether this is the first reply in the active exchange
|
||||||
|
- typing delay per message based on message length
|
||||||
|
|
||||||
|
### Stage F: persistence
|
||||||
|
|
||||||
|
Inbound and outbound messages are stored as `persona.conversation` facts.
|
||||||
|
|
||||||
|
## 5. Adapter boundary
|
||||||
|
|
||||||
|
BoxBrain separates provider-specific API mechanics from runtime logic.
|
||||||
|
|
||||||
|
The framework itself expects abstract adapters for:
|
||||||
|
|
||||||
|
- plain text generation
|
||||||
|
- structured-object generation
|
||||||
|
- image generation
|
||||||
|
- special-date retrieval
|
||||||
|
|
||||||
|
That means the persona runtime can stay largely unchanged while different model providers are swapped underneath.
|
||||||
|
|
||||||
|
## Conversation realism rules encoded in the runtime
|
||||||
|
|
||||||
|
The current runtime already supports several behaviors that matter for realism:
|
||||||
|
|
||||||
|
- first replies can be delayed
|
||||||
|
- follow-up messages in the same exchange can skip that initial reply delay
|
||||||
|
- typing time depends on message length
|
||||||
|
- `do_not_disturb` can probabilistically fail to reply at all
|
||||||
|
- `offline` blocks the turn entirely
|
||||||
|
- a refusal plan can still emit a short farewell sequence and trigger a tool-driven availability change
|
||||||
|
|
||||||
|
## What BoxBrain is not
|
||||||
|
|
||||||
|
BoxBrain is not currently:
|
||||||
|
|
||||||
|
- a SaaS product
|
||||||
|
- an HTTP API server
|
||||||
|
- a complete social simulation world model
|
||||||
|
- a universal provider SDK
|
||||||
|
- a frontend chat application
|
||||||
|
|
||||||
|
It is the runtime foundation you would build those things on top of.
|
||||||
7
_Sidebar.md
Normal file
7
_Sidebar.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# BoxBrain Wiki
|
||||||
|
|
||||||
|
- [Home](Home)
|
||||||
|
- [Purpose and Architecture](Purpose-and-Architecture)
|
||||||
|
- [Getting Started](Getting-Started)
|
||||||
|
- [API Reference](API-Reference)
|
||||||
|
- [xAI Grok Adapter](xAI-Grok-Adapter)
|
||||||
106
xAI-Grok-Adapter.md
Normal file
106
xAI-Grok-Adapter.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# xAI Grok Adapter
|
||||||
|
|
||||||
|
BoxBrain ships with a ready-made xAI Grok adapter set so you can get started without writing your own provider glue first.
|
||||||
|
|
||||||
|
## What it includes
|
||||||
|
|
||||||
|
- `createGrokTextModelAdapter`
|
||||||
|
- `createGrokStructuredModelAdapter`
|
||||||
|
- `createGrokImageModelAdapter`
|
||||||
|
- `createGrokAdapters`
|
||||||
|
|
||||||
|
Provider identifier used by the adapter:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
'xai-grok'
|
||||||
|
```
|
||||||
|
|
||||||
|
Default base URL:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
https://api.x.ai/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bundle setup
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createGrokAdapters } from 'boxbrain';
|
||||||
|
|
||||||
|
const grok = createGrokAdapters({
|
||||||
|
apiKey: process.env.XAI_API_KEY!,
|
||||||
|
textModel: 'grok-4.3-mini',
|
||||||
|
structuredModel: 'grok-4.3',
|
||||||
|
imageModel: 'grok-imagine-image-quality',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Text adapter
|
||||||
|
|
||||||
|
The text adapter sends requests to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /chat/completions
|
||||||
|
```
|
||||||
|
|
||||||
|
It builds a standard message array from:
|
||||||
|
|
||||||
|
- optional `system`
|
||||||
|
- required `prompt`
|
||||||
|
|
||||||
|
## Structured-output adapter
|
||||||
|
|
||||||
|
The structured adapter also uses:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /chat/completions
|
||||||
|
```
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
|
||||||
|
- if `request.schema` exists, it sends `response_format: { type: 'json_schema', ... }`
|
||||||
|
- otherwise, it falls back to `response_format: { type: 'json_object' }`
|
||||||
|
- the returned message content is parsed as JSON
|
||||||
|
|
||||||
|
This is the adapter used by the current BoxBrain persona, schedule, memory-selection, and conversation-plan flows.
|
||||||
|
|
||||||
|
## Image adapter
|
||||||
|
|
||||||
|
The image adapter sends requests to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
POST /images/generations
|
||||||
|
```
|
||||||
|
|
||||||
|
It maps BoxBrain-neutral aspect ratios to xAI payload values like this:
|
||||||
|
|
||||||
|
| BoxBrain | xAI |
|
||||||
|
|---|---|
|
||||||
|
| `square` | `1:1` |
|
||||||
|
| `portrait` | `3:4` |
|
||||||
|
| `landscape` | `16:9` |
|
||||||
|
|
||||||
|
## Advanced options
|
||||||
|
|
||||||
|
Both the per-capability factories and the bundle helper support:
|
||||||
|
|
||||||
|
- `apiKey`
|
||||||
|
- model name(s)
|
||||||
|
- `baseUrl?`
|
||||||
|
- `fetch?`
|
||||||
|
- `extraHeaders?`
|
||||||
|
|
||||||
|
That makes the adapter easy to test or route through custom gateways.
|
||||||
|
|
||||||
|
## Testing approach used in BoxBrain
|
||||||
|
|
||||||
|
The adapter is tested with injected `fetch` functions instead of a vendor SDK. The test suite asserts:
|
||||||
|
|
||||||
|
- URL
|
||||||
|
- HTTP method
|
||||||
|
- auth headers
|
||||||
|
- model selection
|
||||||
|
- chat messages
|
||||||
|
- JSON-schema response format
|
||||||
|
- image aspect-ratio mapping
|
||||||
|
|
||||||
|
This keeps the adapter lightweight and deterministic.
|
||||||
Reference in New Issue
Block a user