Skip to content

BitByte08/PLASMA

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

29 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Plasma Engine

English ยท ํ•œ๊ตญ์–ด


English

Persona Lifecycle & Adaptive Social Mind Architecture

LLM-backed AI persona engine for simulations, virtual agents, and interactive applications.

Each PlasmaEngine instance is one simulated person โ€” they have a personality, mood, fatigue, memory, a social influence score, and a relationship graph centered on themselves. They read messages, decide whether to respond, call external tools, and produce in-character text replies.

Works in any TypeScript environment (Node.js, Electron, browser, etc.). Requires an OpenAI or Anthropic API key supplied at runtime.

Architecture

PlasmaEngine (one instance per persona)
โ”œโ”€โ”€ PersonaCore         โ€” static traits: personality, skills, values, influence score
โ”œโ”€โ”€ EmotionCore         โ€” dynamic mood: valence, arousal, stress, confidence, motivation
โ”œโ”€โ”€ FatigueCore         โ€” energy, mental fatigue, burnout risk; work/rest simulation
โ”œโ”€โ”€ MemoryCore          โ€” episodic/semantic/emotional memories with decay + retrieval
โ”œโ”€โ”€ RelationshipGraph   โ€” ego-centric graph; edges updated by every interaction
โ”‚
โ”œโ”€โ”€ MessageRouter       โ€” engagement decision engine (MUST / SHOULD / CAN / SKIP)
โ”œโ”€โ”€ ConversationManager โ€” conversation state and history
โ”œโ”€โ”€ ResponseGenerator   โ€” agentic loop: LLM โ†’ tool calls โ†’ result โ†’ final reply
โ”‚
โ”œโ”€โ”€ MCPClient           โ€” tool registry; register handlers at startup
โ”œโ”€โ”€ PromptBuilder       โ€” assembles system prompt from all live state
โ””โ”€โ”€ LLMProvider         โ€” OpenAI / Anthropic abstraction

Installation

npm install plasma-engine
# install your chosen LLM provider:
npm install openai             # for OpenAI
npm install @anthropic-ai/sdk  # for Anthropic

Quick Start

import { PlasmaEngine, fromPreset } from 'plasma-engine';

const engine = new PlasmaEngine({
  persona: {
    id: 'dev-jisu',
    name: '๊น€์ง€์ˆ˜',
    role: 'Frontend Developer',
    influenceScore: fromPreset('senior').score,  // 58
    influenceLabel: 'Senior',
    personality: {
      openness: 0.75, conscientiousness: 0.65,
      extraversion: 0.45, agreeableness: 0.70,
      neuroticism: 0.30, ambition: 0.65,
    },
    background: '5-year frontend developer. React and TypeScript specialist.',
    skills: [
      { name: 'React',      level: 'expert',       domain: 'frontend' },
      { name: 'TypeScript', level: 'advanced',      domain: 'frontend' },
      { name: 'UI Design',  level: 'intermediate',  domain: 'design' },
    ],
    communicationStyle: { formality: 0.35, verbosity: 0.55, directness: 0.6, humor: 0.4, language: 'en' },
    relationships: [
      { personaId: 'lead-1', name: 'Team Lead', trust: 0.7, rapport: 0.6, influenceScore: 99, explicitType: 'superior' },
      { personaId: 'dev-2',  name: 'Park Minsu', trust: 0.8, rapport: 0.8, influenceScore: 40 },
    ],
    values: ['code quality', 'autonomy', 'learning'],
  },
  llm: {
    provider: 'anthropic',
    apiKey: process.env.ANTHROPIC_API_KEY!,
  },
  debug: false,
});

Register external tools

Plug in any callable action โ€” databases, APIs, task queues, etc.:

engine.registerTool(
  {
    name: 'get_task_queue',
    description: 'List tasks currently assigned to this persona.',
    inputSchema: { type: 'object', properties: {} },
  },
  async () => myApp.getTasksFor(engine.persona.id)
);

Handle incoming messages

const decision = engine.routeMessage({
  id: 'msg-001',
  conversationId: 'channel-general',
  senderId: 'lead-1',
  senderName: 'Team Lead',
  senderInfluenceScore: 99,
  content: 'Jisu, is the sprint review ready?',
  timestamp: Date.now(),
  mentions: ['dev-jisu'],
  isDirectMessage: false,
});

// decision.action: 'MUST_RESPOND' | 'SHOULD_RESPOND' | 'CAN_RESPOND' | 'SKIP'
if (decision.action !== 'SKIP') {
  setTimeout(async () => {
    const reply = await engine.respond(message);
    myApp.sendMessage(engine.persona.id, reply);
  }, decision.delayMs);
}

Lifecycle events โ†’ emotion

engine.applyGameEvent({ type: 'praised',         intensity: 0.9 });
engine.applyGameEvent({ type: 'deadline_missed', intensity: 0.7 });
engine.applyGameEvent({ type: 'promoted',        intensity: 1.0 });

Time simulation

engine.advanceTime(8, false);  // 8 hours of work
engine.advanceTime(6, true);   // 6 hours of rest
engine.startNewDay();
engine.startNewWeek();

Events

engine.on('emotion:changed',      (state) => ui.updateMoodIndicator(state.mood));
engine.on('fatigue:changed',      (state) => ui.updateEnergyBar(state.energy));
engine.on('engagement:decided',   (d)     => console.log(d.action, d.reasoning));
engine.on('relationship:updated', (edge)  => updateGraphNode(edge));
engine.on('memory:added',         (mem)   => console.log('Memory:', mem.content));
engine.on('response:generated',   ({ response }) => console.log(response));

Social Influence System

Instead of hardcoded job titles, Plasma uses a 0โ€“100 influence score:

import { INFLUENCE_PRESETS, fromPreset, deriveModifiers } from 'plasma-engine';

INFLUENCE_PRESETS.intern        // 5
INFLUENCE_PRESETS.senior        // 58
INFLUENCE_PRESETS.team_manager  // 80
INFLUENCE_PRESETS.ceo_founder   // 99

const jisu = fromPreset('senior', 'Senior');  // โ†’ { score: 58, label: 'Senior' }
const mods = deriveModifiers(58);
// mods.decisionAuthority      โ‰ˆ 0.52
// mods.deferenceToHigher      โ‰ˆ 0.44
// mods.maxEffectiveHoursPerDay โ‰ˆ 11.5

Relationship Graph

Metric Range Meaning
trust 0โ€“1 Reliability and honesty
rapport 0โ€“1 Warmth and social comfort
respect 0โ€“1 Admiration of competence/character
tension 0โ€“1 Conflict and friction
familiarity 0โ€“1 How well they know each other

Auto-derived types: close_friend ยท friend ยท colleague ยท rival ยท acquaintance ยท stranger

API Reference

Method Description
routeMessage(msg) Feed a message โ†’ returns EngagementDecision
respond(msg, opts?) Generate and record a response string
logInteraction(targetId, name, event) Record an interaction (updates relationship graph)
setRelationshipType(id, name, type, meta?) Set explicit relationship classification
getEgoGraph() Export graph for visualization
applyGameEvent(event) Trigger emotional/memory update
advanceTime(hours, isResting?) Simulate passage of time
registerTool(tool, handler) Register an external tool
serialize() Full serialisable state for save/load
.on(event, handler) Subscribe to engine events

Save / Load

const saved = engine.serialize();
localStorage.setItem('persona-dev-jisu', JSON.stringify(saved));
// Full restore API planned for v0.3

ํ•œ๊ตญ์–ด

Persona Lifecycle & Adaptive Social Mind Architecture

LLM ๊ธฐ๋ฐ˜ AI ํŽ˜๋ฅด์†Œ๋‚˜ ์—”์ง„ โ€” ๊ฐ์ •ยทํ”ผ๋กœ๋„ยท๊ธฐ์–ตยท์ธ๊ฐ„๊ด€๊ณ„๋ฅผ ๊ฐ€์ง„ ๊ฐ€์ƒ ์ธ๊ฒฉ์ด ์ƒํ™ฉ์— ๋งž๊ฒŒ ์ž์œจ์ ์œผ๋กœ ํŒ๋‹จํ•˜๊ณ  ๋Œ€ํ™”ํ•ฉ๋‹ˆ๋‹ค.

PlasmaEngine ์ธ์Šคํ„ด์Šค ํ•˜๋‚˜๊ฐ€ ๊ณง ํ•œ ๋ช…์˜ ๊ฐ€์ƒ ์ธ๊ฒฉ์ž…๋‹ˆ๋‹ค. ์„ฑ๊ฒฉ, ๊ธฐ๋ถ„, ํ”ผ๋กœ๋„, ๊ธฐ์–ต, ์‚ฌํšŒ์  ์˜ํ–ฅ๋ ฅ ์ ์ˆ˜, ๊ทธ๋ฆฌ๊ณ  ๊ด€๊ณ„ ๊ทธ๋ž˜ํ”„๋ฅผ ๊ฐ€์ง€๋ฉฐ, ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ๊ณ  ์‘๋‹ต ์—ฌ๋ถ€๋ฅผ ์Šค์Šค๋กœ ํŒ๋‹จํ•ด ์บ๋ฆญํ„ฐ์— ๋งž๋Š” ์‘๋‹ต์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

TypeScript ํ™˜๊ฒฝ์ด๋ผ๋ฉด ์–ด๋””์„œ๋“  ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค (Node.js, Electron, ๋ธŒ๋ผ์šฐ์ € ๋“ฑ). OpenAI ๋˜๋Š” Anthropic API ํ‚ค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

์•„ํ‚คํ…์ฒ˜

PlasmaEngine (ํŽ˜๋ฅด์†Œ๋‚˜๋‹น 1๊ฐœ ์ธ์Šคํ„ด์Šค)
โ”œโ”€โ”€ PersonaCore         โ€” ์ •์  ํŠน์„ฑ: ์„ฑ๊ฒฉ, ์Šคํ‚ฌ, ๊ฐ€์น˜๊ด€, ์˜ํ–ฅ๋ ฅ ์ ์ˆ˜
โ”œโ”€โ”€ EmotionCore         โ€” ๋™์  ๊ฐ์ •: ๊ฐ๊ฐ€, ๊ฐ์„ฑ๋„, ์ŠคํŠธ๋ ˆ์Šค, ์ž์‹ ๊ฐ, ๋™๊ธฐ๋ถ€์—ฌ
โ”œโ”€โ”€ FatigueCore         โ€” ์—๋„ˆ์ง€, ์ •์‹  ํ”ผ๋กœ, ๋ฒˆ์•„์›ƒ ์œ„ํ—˜; ๊ทผ๋ฌด/ํœด์‹ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
โ”œโ”€โ”€ MemoryCore          โ€” ์—ํ”ผ์†Œ๋“œ/์˜๋ฏธ๋ก ์ /๊ฐ์ • ๊ธฐ์–ต (๊ฐ์‡  + ๊ฒ€์ƒ‰)
โ”œโ”€โ”€ RelationshipGraph   โ€” ์ž๊ธฐ์ค‘์‹ฌ ๊ทธ๋ž˜ํ”„; ๋ชจ๋“  ์ƒํ˜ธ์ž‘์šฉ์œผ๋กœ ๊ฐฑ์‹ 
โ”‚
โ”œโ”€โ”€ MessageRouter       โ€” ์ฐธ์—ฌ ๊ฒฐ์ • ์—”์ง„ (MUST / SHOULD / CAN / SKIP)
โ”œโ”€โ”€ ConversationManager โ€” ๋Œ€ํ™” ์ƒํƒœ ๋ฐ ํžˆ์Šคํ† ๋ฆฌ
โ”œโ”€โ”€ ResponseGenerator   โ€” ์—์ด์ „ํŠธ ๋ฃจํ”„: LLM โ†’ ๋„๊ตฌ ํ˜ธ์ถœ โ†’ ๊ฒฐ๊ณผ โ†’ ์ตœ์ข… ์‘๋‹ต
โ”‚
โ”œโ”€โ”€ MCPClient           โ€” ๋„๊ตฌ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ; ์‹œ์ž‘ ์‹œ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก
โ”œโ”€โ”€ PromptBuilder       โ€” ๋ชจ๋“  ๋ผ์ด๋ธŒ ์ƒํƒœ๋ฅผ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋กœ ์กฐํ•ฉ
โ””โ”€โ”€ LLMProvider         โ€” OpenAI / Anthropic ์ถ”์ƒํ™”

์„ค์น˜

npm install plasma-engine
# ์‚ฌ์šฉํ•  LLM ํ”„๋กœ๋ฐ”์ด๋” ์„ค์น˜:
npm install openai             # OpenAI
npm install @anthropic-ai/sdk  # Anthropic

๋น ๋ฅธ ์‹œ์ž‘

import { PlasmaEngine, fromPreset } from 'plasma-engine';

const engine = new PlasmaEngine({
  persona: {
    id: 'dev-jisu',
    name: '๊น€์ง€์ˆ˜',
    role: 'Frontend Developer',
    influenceScore: fromPreset('senior').score,  // 58
    influenceLabel: '๊ณผ์žฅ',
    personality: {
      openness: 0.75, conscientiousness: 0.65,
      extraversion: 0.45, agreeableness: 0.70,
      neuroticism: 0.30, ambition: 0.65,
    },
    background: '5๋…„์ฐจ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž. React์™€ TypeScript ์ „๋ฌธ๊ฐ€.',
    skills: [
      { name: 'React',      level: 'expert',       domain: 'frontend' },
      { name: 'TypeScript', level: 'advanced',      domain: 'frontend' },
      { name: 'UI Design',  level: 'intermediate',  domain: 'design' },
    ],
    communicationStyle: {
      formality: 0.35, verbosity: 0.55,
      directness: 0.6, humor: 0.4, language: 'ko',
    },
    relationships: [
      { personaId: 'lead-1', name: 'ํŒ€์žฅ', trust: 0.7, rapport: 0.6, influenceScore: 99, explicitType: 'superior' },
      { personaId: 'dev-2',  name: '๋ฐ•๋ฏผ์ˆ˜', trust: 0.8, rapport: 0.8, influenceScore: 40 },
    ],
    values: ['code quality', 'autonomy', 'learning'],
  },
  llm: {
    provider: 'openai',
    apiKey: process.env.OPENAI_API_KEY!,
  },
  debug: false,
});

์™ธ๋ถ€ ๋„๊ตฌ ๋“ฑ๋ก

DB, API, ์ž‘์—… ํ ๋“ฑ ์›ํ•˜๋Š” ์•ก์…˜์„ ์ž์œ ๋กญ๊ฒŒ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

engine.registerTool(
  {
    name: 'get_task_queue',
    description: '์ด ํŽ˜๋ฅด์†Œ๋‚˜์—๊ฒŒ ํ• ๋‹น๋œ ์ž‘์—… ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.',
    inputSchema: { type: 'object', properties: {} },
  },
  async () => myApp.getTasksFor(engine.persona.id)
);

๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ

const decision = engine.routeMessage({
  id: 'msg-001',
  conversationId: 'channel-general',
  senderId: 'lead-1',
  senderName: 'ํŒ€์žฅ',
  senderInfluenceScore: 99,
  content: '์ง€์ˆ˜์”จ, ์˜ค๋Š˜ ์Šคํ”„๋ฆฐํŠธ ๋ฆฌ๋ทฐ ์ค€๋น„๋๋‚˜์š”?',
  timestamp: Date.now(),
  mentions: ['dev-jisu'],
  isDirectMessage: false,
});

// decision.action: 'MUST_RESPOND' | 'SHOULD_RESPOND' | 'CAN_RESPOND' | 'SKIP'
if (decision.action !== 'SKIP') {
  setTimeout(async () => {
    const reply = await engine.respond(message);
    myApp.sendMessage(engine.persona.id, reply);
  }, decision.delayMs);
}

๋ผ์ดํ”„์‚ฌ์ดํด ์ด๋ฒคํŠธ โ†’ ๊ฐ์ •

engine.applyGameEvent({ type: 'praised',         intensity: 0.9 });
engine.applyGameEvent({ type: 'deadline_missed', intensity: 0.7 });
engine.applyGameEvent({ type: 'promoted',        intensity: 1.0 });

์‹œ๊ฐ„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜

engine.advanceTime(8, false);  // 8์‹œ๊ฐ„ ๊ทผ๋ฌด
engine.advanceTime(6, true);   // 6์‹œ๊ฐ„ ํœด์‹
engine.startNewDay();
engine.startNewWeek();

์ด๋ฒคํŠธ ๊ตฌ๋…

engine.on('emotion:changed',      (state) => ui.updateMoodIndicator(state.mood));
engine.on('fatigue:changed',      (state) => ui.updateEnergyBar(state.energy));
engine.on('engagement:decided',   (d)     => console.log(d.action, d.reasoning));
engine.on('relationship:updated', (edge)  => updateGraphNode(edge));
engine.on('memory:added',         (mem)   => console.log('๊ธฐ์–ต:', mem.content));
engine.on('response:generated',   ({ response }) => console.log(response));

์‚ฌํšŒ์  ์˜ํ–ฅ๋ ฅ ์‹œ์Šคํ…œ

์ง๊ธ‰ ์ด๋ฆ„ ๋Œ€์‹  0~100 ์˜ํ–ฅ๋ ฅ ์ ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

import { INFLUENCE_PRESETS, fromPreset, deriveModifiers } from 'plasma-engine';

INFLUENCE_PRESETS.intern        // 5
INFLUENCE_PRESETS.senior        // 58
INFLUENCE_PRESETS.team_manager  // 80
INFLUENCE_PRESETS.ceo_founder   // 99

const jisu = fromPreset('senior', '๊ณผ์žฅ');  // โ†’ { score: 58, label: '๊ณผ์žฅ' }
const mods = deriveModifiers(58);
// mods.decisionAuthority      โ‰ˆ 0.52
// mods.deferenceToHigher      โ‰ˆ 0.44
// mods.maxEffectiveHoursPerDay โ‰ˆ 11.5

๊ด€๊ณ„ ๊ทธ๋ž˜ํ”„

์ง€ํ‘œ ๋ฒ”์œ„ ์˜๋ฏธ
trust 0โ€“1 ์‹ ๋ขฐ๋„
rapport 0โ€“1 ์นœ๋ฐ€๊ฐ
respect 0โ€“1 ์กด๊ฒฝ
tension 0โ€“1 ๊ฐˆ๋“ฑ
familiarity 0โ€“1 ์นœ์ˆ™ํ•จ

์ž๋™ ๋ถ„๋ฅ˜: close_friend ยท friend ยท colleague ยท rival ยท acquaintance ยท stranger

API ๋ ˆํผ๋Ÿฐ์Šค

๋ฉ”์„œ๋“œ ์„ค๋ช…
routeMessage(msg) ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  โ†’ EngagementDecision ๋ฐ˜ํ™˜
respond(msg, opts?) ์‘๋‹ต ๋ฌธ์ž์—ด ์ƒ์„ฑ ๋ฐ ๊ธฐ๋ก
logInteraction(targetId, name, event) ์ƒํ˜ธ์ž‘์šฉ ๊ธฐ๋ก (๊ด€๊ณ„ ๊ทธ๋ž˜ํ”„ ์—…๋ฐ์ดํŠธ)
setRelationshipType(id, name, type, meta?) ๊ด€๊ณ„ ์œ ํ˜• ๋ช…์‹œ์  ์„ค์ •
getEgoGraph() ์‹œ๊ฐํ™”์šฉ ๊ทธ๋ž˜ํ”„ ๋‚ด๋ณด๋‚ด๊ธฐ
applyGameEvent(event) ๊ฐ์ •/๊ธฐ์–ต ์—…๋ฐ์ดํŠธ ํŠธ๋ฆฌ๊ฑฐ
advanceTime(hours, isResting?) ์‹œ๊ฐ„ ๊ฒฝ๊ณผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
registerTool(tool, handler) ์™ธ๋ถ€ ๋„๊ตฌ ๋“ฑ๋ก
serialize() ์ €์žฅ/๋กœ๋“œ์šฉ ์ „์ฒด ์ง๋ ฌํ™”
.on(event, handler) ์—”์ง„ ์ด๋ฒคํŠธ ๊ตฌ๋…

์ €์žฅ / ๋กœ๋“œ

const saved = engine.serialize();
localStorage.setItem('persona-dev-jisu', JSON.stringify(saved));
// ์ „์ฒด ๋ณต์› API๋Š” v0.3์—์„œ ๊ณ„ํš ์ค‘

About

Persona Lifecycle & Adaptive Social Mind Architecture

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors