2 Commits

Author SHA1 Message Date
05f077b798 v0.4.0
All checks were successful
CI / verify (push) Successful in 12s
Publish / publish (push) Successful in 21s
2026-05-19 23:24:42 +09:00
f964d4de9b feat: add baseSystemPrompt 2026-05-19 23:24:34 +09:00
6 changed files with 50 additions and 7 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "boxbrain", "name": "boxbrain",
"version": "0.3.5", "version": "0.4.0",
"description": "Human-like persona harness framework powered by LLMs and IdentityDB.", "description": "Human-like persona harness framework powered by LLMs and IdentityDB.",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",

View File

@@ -20,14 +20,16 @@ export function formatMessageHistory(input: {
.join("\n"); .join("\n");
} }
export function conversationInstruction(): string { export function conversationInstruction(baseSystemPrompt?: string): string {
return [ const parts = [
...(baseSystemPrompt === undefined ? [] : [baseSystemPrompt]),
"You are controlling the persona, not a generic assistant.", "You are controlling the persona, not a generic assistant.",
"Use the send_message tool conceptually: return one or more outgoing messages.", "Use the send_message tool conceptually: return one or more outgoing messages.",
"Unless the persona strongly prefers otherwise, keep each outgoing message to at most one sentence.", "Unless the persona strongly prefers otherwise, keep each outgoing message to at most one sentence.",
"Prefer short, natural, chat-like wording and allow splitting one thought into multiple messages.", "Prefer short, natural, chat-like wording and allow splitting one thought into multiple messages.",
'If mandatory memory says "기억이 없음", the persona may naturally wonder about missing context instead of pretending to remember.', 'If mandatory memory says "기억이 없음", the persona may naturally wonder about missing context instead of pretending to remember.',
].join("\n"); ];
return parts.join("\n");
} }
export async function buildMandatoryConversationContext(input: { export async function buildMandatoryConversationContext(input: {

View File

@@ -68,6 +68,7 @@ export class Persona {
private readonly mode: Mode; private readonly mode: Mode;
private readonly readyPromise: Promise<MemorySpace>; private readonly readyPromise: Promise<MemorySpace>;
private availabilitySnapshot?: ScheduledAvailabilitySnapshot; private availabilitySnapshot?: ScheduledAvailabilitySnapshot;
readonly baseSystemPrompt: string | undefined;
constructor( constructor(
displayName: string, displayName: string,
@@ -88,6 +89,7 @@ export class Persona {
this.options = second ?? {}; this.options = second ?? {};
} }
this.memory = this.options.memory ?? new InMemoryMemoryStore(); this.memory = this.options.memory ?? new InMemoryMemoryStore();
this.baseSystemPrompt = this.options.baseSystemPrompt;
this.readyPromise = this.initialize(); this.readyPromise = this.initialize();
} }
@@ -247,7 +249,7 @@ export class Persona {
mode: "reply", mode: "reply",
context, context,
...(userMessage === undefined ? {} : { userMessage }), ...(userMessage === undefined ? {} : { userMessage }),
instruction: conversationInstruction(), instruction: conversationInstruction(this.baseSystemPrompt),
}), }),
); );
@@ -281,7 +283,7 @@ export class Persona {
now: toIso(input.datetime), now: toIso(input.datetime),
mode: "reply", mode: "reply",
context: latestContext, context: latestContext,
instruction: conversationInstruction(), instruction: conversationInstruction(this.baseSystemPrompt),
})), })),
); );
} }
@@ -320,7 +322,7 @@ export class Persona {
now: toIso(input.datetime), now: toIso(input.datetime),
mode: "start-conversation", mode: "start-conversation",
context, context,
instruction: conversationInstruction(), instruction: conversationInstruction(this.baseSystemPrompt),
}), }),
); );
await this.emit("persona.conversation.started", { await this.emit("persona.conversation.started", {

View File

@@ -160,6 +160,7 @@ export interface PersonaOptions {
models?: PersonaModels; models?: PersonaModels;
debug?: DebugHook; debug?: DebugHook;
now?: DateTimeInput; now?: DateTimeInput;
baseSystemPrompt?: string;
} }
export interface BoxBrainMemoryStore { export interface BoxBrainMemoryStore {

View File

@@ -124,4 +124,30 @@ describe('Conversation API', () => {
expect(mode).toBe('start-conversation'); expect(mode).toBe('start-conversation');
expect(started.messages).toEqual(['오늘 좀 조용하네.']); expect(started.messages).toEqual(['오늘 좀 조용하네.']);
}); });
it('includes baseSystemPrompt at the start of the instruction when provided', async () => {
const memory = new InMemoryMemoryStore();
let captured: ReplyGenerationInput | undefined;
const persona = new Persona('Mina', 'Mina likes quiet cafes.', {
memory,
now: '2026-05-01T10:00:00.000Z',
baseSystemPrompt: 'You are a helpful assistant. Always be kind.',
models: {
conversation: {
async generateReply(input) {
captured = input;
return { messages: ['Hello!'] };
},
},
},
});
await persona.ready();
await persona.sendMessage({
datetime: '2026-05-01T12:00:00.000Z',
messageHistory: [{ sender: 'user', time: '2026-05-01T12:00:00.000Z', content: 'Hi' }],
});
expect(captured?.instruction.startsWith('You are a helpful assistant. Always be kind.')).toBe(true);
});
}); });

View File

@@ -29,6 +29,18 @@ describe('Persona initialization', () => {
expect(debug).toContain('persona.initialized'); expect(debug).toContain('persona.initialized');
}); });
it('exposes baseSystemPrompt on the persona instance when provided', async () => {
const memory = new InMemoryMemoryStore();
const persona = new Persona('Hana', 'Hana is a cheerful barista.', {
memory,
now: '2026-05-01T10:00:00.000Z',
baseSystemPrompt: 'You are a helpful assistant. Always be kind.',
});
await persona.ready();
expect(persona.baseSystemPrompt).toBe('You are a helpful assistant. Always be kind.');
});
it('loads an existing persona space by space id without creating another space', async () => { it('loads an existing persona space by space id without creating another space', async () => {
const memory = new InMemoryMemoryStore(); const memory = new InMemoryMemoryStore();
const created = new Persona('Joon', 'Joon is a freelance designer.', { memory, now: '2026-05-01T10:00:00.000Z' }); const created = new Persona('Joon', 'Joon is a freelance designer.', { memory, now: '2026-05-01T10:00:00.000Z' });