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