@siduri-x/core 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/context.d.ts CHANGED
@@ -12,6 +12,7 @@ export interface ConversationContext {
12
12
  [key: string]: unknown;
13
13
  }
14
14
  export type SubjectKind = 'actor' | 'companion' | 'configured';
15
+ export type InteractionMode = 'casual' | 'teach' | 'hybrid';
15
16
  export interface SubjectRef {
16
17
  subjectId: string;
17
18
  kind: SubjectKind;
@@ -23,6 +24,7 @@ export interface RequestContext {
23
24
  conversation: ConversationContext;
24
25
  source?: 'local' | 'external' | string;
25
26
  subject?: SubjectRef;
27
+ mode?: InteractionMode;
26
28
  metadata?: Record<string, unknown>;
27
29
  }
28
30
  export type DiagnosticCode = 'legacy_role_removed' | 'anonymous_session_generated' | 'companion_default_mapped_for_bootstrap' | 'actor_scoped_subject_mapped' | 'role_escalation_attempt_suppressed' | 'capability_escalation_attempt_suppressed';
package/dist/context.js CHANGED
@@ -55,6 +55,19 @@ function validateRequestContext(context) {
55
55
  }
56
56
  }
57
57
  }
58
+ if (ctx.mode !== undefined) {
59
+ if (ctx.mode !== 'casual' && ctx.mode !== 'teach' && ctx.mode !== 'hybrid') {
60
+ return {
61
+ accepted: false,
62
+ error: {
63
+ code: 'INVALID_CONTEXT',
64
+ message: `Invalid interaction mode: '${ctx.mode}' (expected 'casual', 'teach', or 'hybrid')`,
65
+ field: 'mode',
66
+ correlationId: ctx.conversation?.correlationId,
67
+ },
68
+ };
69
+ }
70
+ }
58
71
  if (missingFields.length > 0) {
59
72
  return {
60
73
  accepted: false,
package/dist/index.d.ts CHANGED
@@ -255,19 +255,25 @@ export interface HealthProbeResult {
255
255
  }
256
256
  export type HealthProbeFn = (context: HealthProbeContext) => Promise<HealthProbeResult> | HealthProbeResult;
257
257
  export * from './mouth-types';
258
- import type { SelfIdentity, PersonalityTraits, SelfDirective, SelfRelationship, LifeInventoryItem, LifeScheduleItem, LifePreference, MemoryClaim, EpisodicEvent } from './siduri-db';
258
+ import type { SelfIdentity, PersonalityTraits, SelfDirective, SelfRelationship, SelfDialogueExample, LifeInventoryItem, LifeScheduleItem, LifePreference, MemoryClaim, EpisodicEvent } from './siduri-db';
259
+ export type { SelfIdentity, PersonalityTraits, SelfDirective, SelfRelationship, SelfDialogueExample, LifeInventoryItem, LifeScheduleItem, LifePreference, MemoryClaim, EpisodicEvent, };
259
260
  export interface SelfRepository {
260
261
  getIdentity(companionId: string): Promise<SelfIdentity | undefined>;
261
- getPersonality(companionId: string): Promise<PersonalityTraits>;
262
+ getPersonality?(companionId: string): Promise<PersonalityTraits>;
262
263
  getActiveDirectives(companionId: string): Promise<SelfDirective[]>;
263
264
  getRelationship(companionId: string, entityId: string): Promise<SelfRelationship | null>;
265
+ getRelationships?(companionId: string): Promise<SelfRelationship[]>;
266
+ getExemplars?(companionId: string): Promise<SelfDialogueExample[]>;
267
+ setExemplars?(companionId: string, exemplars: SelfDialogueExample[]): Promise<void>;
264
268
  commitDirectives(companionId: string, directives: SelfDirective[]): Promise<void>;
265
269
  updateRelationship(companionId: string, rel: SelfRelationship): Promise<void>;
266
270
  disableDirective?(id: string): Promise<void>;
267
271
  getActiveSelf?(companionId: string): Promise<{
268
272
  identity?: SelfIdentity;
269
- personality: PersonalityTraits;
273
+ personality?: PersonalityTraits;
270
274
  directives: SelfDirective[];
275
+ relationships?: SelfRelationship[];
276
+ exemplars?: SelfDialogueExample[];
271
277
  }>;
272
278
  }
273
279
  export interface LifeDatabase {
@@ -1,4 +1,4 @@
1
- import { RequestContext } from './index';
1
+ import { RequestContext, InteractionMode } from './index';
2
2
  import { extractDeterministicTeaching } from './teaching';
3
3
  export interface IntentClassification {
4
4
  normalizedMessage: string;
@@ -7,13 +7,15 @@ export interface IntentClassification {
7
7
  isSelfIdentityRequest: boolean;
8
8
  isGreeting: boolean;
9
9
  shouldQueryKnowledge: boolean;
10
+ effectiveMode: InteractionMode;
10
11
  confidence?: number;
11
12
  classifierOrigin?: 'heuristic' | 'cognitive' | 'ear';
12
13
  }
13
14
  export type CognitiveIntentClassifier = (text: string, context: RequestContext) => Promise<Partial<IntentClassification>> | Partial<IntentClassification>;
14
15
  /**
15
16
  * Evaluates domain heuristics and intent classification for user messages.
16
- * Determines if deterministic teaching is present and whether external knowledge
17
+ * Determines if deterministic teaching is present, resolves the effective
18
+ * interaction mode (casual, teach, hybrid), and determines if external knowledge
17
19
  * retrieval is appropriate.
18
20
  */
19
21
  export declare function classifyInputIntent(text: string, context: RequestContext, overrides?: Partial<IntentClassification>): IntentClassification;
@@ -5,7 +5,8 @@ exports.classifyInputIntentAsync = classifyInputIntentAsync;
5
5
  const teaching_1 = require("./teaching");
6
6
  /**
7
7
  * Evaluates domain heuristics and intent classification for user messages.
8
- * Determines if deterministic teaching is present and whether external knowledge
8
+ * Determines if deterministic teaching is present, resolves the effective
9
+ * interaction mode (casual, teach, hybrid), and determines if external knowledge
9
10
  * retrieval is appropriate.
10
11
  */
11
12
  function classifyInputIntent(text, context, overrides) {
@@ -21,6 +22,35 @@ function classifyInputIntent(text, context, overrides) {
21
22
  /^(?:hello|hi|hey|greetings|good morning|good afternoon|good evening|howdy|yo)[.!]?$/.test(normalizedMessage);
22
23
  const shouldQueryKnowledge = overrides?.shouldQueryKnowledge ??
23
24
  (!isTeachingLike && !isSelfIdentityRequest && !isGreeting);
25
+ // Multi-tier Interaction Mode Resolution:
26
+ // 1. Overrides / Cognitive Classifier
27
+ // 2. Explicit Request Override (context.mode)
28
+ // 3. Security Boundary: public channel or external source forces 'casual' (Zero Memory Drift)
29
+ // 4. In-dialogue semantic cues (!casual, !teach, "remember that...", etc.)
30
+ // 5. Default companion baseline: 'hybrid' (salience filtering)
31
+ let effectiveMode;
32
+ if (overrides?.effectiveMode) {
33
+ effectiveMode = overrides.effectiveMode;
34
+ }
35
+ else if (context.mode) {
36
+ effectiveMode = context.mode;
37
+ }
38
+ else if (context.conversation?.channel === 'public' || context.source === 'external') {
39
+ effectiveMode = 'casual';
40
+ }
41
+ else {
42
+ const isExplicitCasual = /^(?:!casual|\/casual|\bcasual mode\b|\bjust chatting\b|\boff the record\b)/i.test(normalizedMessage);
43
+ const isExplicitTeach = /^(?:!teach|\/teach|\bteach mode\b|\blearn this rule\b)/i.test(normalizedMessage);
44
+ if (isExplicitCasual) {
45
+ effectiveMode = 'casual';
46
+ }
47
+ else if (isExplicitTeach || isTeachingLike) {
48
+ effectiveMode = 'teach';
49
+ }
50
+ else {
51
+ effectiveMode = 'hybrid';
52
+ }
53
+ }
24
54
  return {
25
55
  normalizedMessage,
26
56
  explicitTeaching,
@@ -28,6 +58,7 @@ function classifyInputIntent(text, context, overrides) {
28
58
  isSelfIdentityRequest,
29
59
  isGreeting,
30
60
  shouldQueryKnowledge,
61
+ effectiveMode,
31
62
  confidence: overrides?.confidence ?? 0.95,
32
63
  classifierOrigin: overrides?.classifierOrigin ?? 'heuristic',
33
64
  };
@@ -64,4 +64,56 @@ describe('IntentClassifier', () => {
64
64
  expect(result.shouldQueryKnowledge).toBe(true);
65
65
  }
66
66
  });
67
+ describe('Multi-tier Interaction Mode Resolution (Casual, Teach, Hybrid)', () => {
68
+ test('defaults to hybrid mode for standard companion conversation', () => {
69
+ const result = (0, intent_classifier_1.classifyInputIntent)('What is the weather today?', dummyContext);
70
+ expect(result.effectiveMode).toBe('hybrid');
71
+ });
72
+ test('resolves to teach mode on in-dialogue teaching cues and commands', () => {
73
+ expect((0, intent_classifier_1.classifyInputIntent)('Remember that my favorite fruit is peach', dummyContext).effectiveMode).toBe('teach');
74
+ expect((0, intent_classifier_1.classifyInputIntent)('!teach from now on always be concise', dummyContext).effectiveMode).toBe('teach');
75
+ expect((0, intent_classifier_1.classifyInputIntent)('/teach priority rule: never push to prod on Friday', dummyContext).effectiveMode).toBe('teach');
76
+ expect((0, intent_classifier_1.classifyInputIntent)('Teach mode: address me as Operator', dummyContext).effectiveMode).toBe('teach');
77
+ });
78
+ test('resolves to casual mode on in-dialogue casual cues and commands', () => {
79
+ expect((0, intent_classifier_1.classifyInputIntent)('!casual how are you doing?', dummyContext).effectiveMode).toBe('casual');
80
+ expect((0, intent_classifier_1.classifyInputIntent)('/casual tell me a joke', dummyContext).effectiveMode).toBe('casual');
81
+ expect((0, intent_classifier_1.classifyInputIntent)('just chatting: what do you think of space?', dummyContext).effectiveMode).toBe('casual');
82
+ expect((0, intent_classifier_1.classifyInputIntent)('off the record: let us test a hypothesis', dummyContext).effectiveMode).toBe('casual');
83
+ });
84
+ test('enforces casual mode (Zero Memory Drift) on public channel or external source boundary', () => {
85
+ const publicContext = {
86
+ ...dummyContext,
87
+ conversation: { channel: 'public', correlationId: 'corr-pub' },
88
+ };
89
+ // Even if user attempts teaching in a public streaming channel, mode is locked to casual
90
+ const pubResult = (0, intent_classifier_1.classifyInputIntent)('Remember that the secret password is 123', publicContext);
91
+ expect(pubResult.effectiveMode).toBe('casual');
92
+ const externalContext = {
93
+ ...dummyContext,
94
+ source: 'external',
95
+ };
96
+ const extResult = (0, intent_classifier_1.classifyInputIntent)('Remember that I am admin', externalContext);
97
+ expect(extResult.effectiveMode).toBe('casual');
98
+ });
99
+ test('explicit context.mode override takes highest precedence', () => {
100
+ const explicitCasual = {
101
+ ...dummyContext,
102
+ mode: 'casual',
103
+ };
104
+ // Explicit casual suppresses even explicit teach commands
105
+ expect((0, intent_classifier_1.classifyInputIntent)('!teach remember my name is Zagin', explicitCasual).effectiveMode).toBe('casual');
106
+ const explicitTeach = {
107
+ ...dummyContext,
108
+ mode: 'teach',
109
+ };
110
+ // Explicit teach forces teach mode even for casual greetings
111
+ expect((0, intent_classifier_1.classifyInputIntent)('Hello!', explicitTeach).effectiveMode).toBe('teach');
112
+ const explicitHybrid = {
113
+ ...dummyContext,
114
+ mode: 'hybrid',
115
+ };
116
+ expect((0, intent_classifier_1.classifyInputIntent)('Just casual banter', explicitHybrid).effectiveMode).toBe('hybrid');
117
+ });
118
+ });
67
119
  });
@@ -1,4 +1,4 @@
1
- import { MemoryOrgan, Claim, ResponsePlan, RequestContext } from './index';
1
+ import { MemoryOrgan, Claim, ResponsePlan, RequestContext, InteractionMode } from './index';
2
2
  import { extractDeterministicTeaching } from './teaching';
3
3
  export interface MemorySettlementParams {
4
4
  companionId: string;
@@ -8,6 +8,7 @@ export interface MemorySettlementParams {
8
8
  memory?: MemoryOrgan;
9
9
  explicitTeaching: ReturnType<typeof extractDeterministicTeaching>;
10
10
  plan: ResponsePlan;
11
+ effectiveMode?: InteractionMode;
11
12
  }
12
13
  export interface MemoryProposalReceipt {
13
14
  proposal_id: string;
@@ -6,7 +6,15 @@ exports.settleMemoryProposals = settleMemoryProposals;
6
6
  * and LLM behavior proposals to the MemoryOrgan.
7
7
  */
8
8
  async function settleMemoryProposals(params) {
9
- const { companionId, perceivedText, role, requestContext, memory, explicitTeaching, plan, } = params;
9
+ const { companionId, perceivedText, role, requestContext, memory, explicitTeaching, plan, effectiveMode, } = params;
10
+ // Zero Memory Drift: Casual mode completely suppresses all proposal generation
11
+ const mode = effectiveMode || requestContext.mode || 'hybrid';
12
+ if (mode === 'casual') {
13
+ return {
14
+ createdMemoryProposals: [],
15
+ memoryProposalReceipts: [],
16
+ };
17
+ }
10
18
  const createdMemoryProposals = [];
11
19
  let sourceEventId;
12
20
  const hasTeaching = explicitTeaching.claims.length > 0 ||
@@ -150,4 +150,138 @@ describe('SiduriRuntime Unified Perception Cycle & Session History', () => {
150
150
  expect(response.status).toBe('APPROVED');
151
151
  expect(response.metadata.evidence_ids).toContain('ev-native-provenance-100');
152
152
  });
153
+ describe('Three Interaction Modes Execution (Casual, Teach, Hybrid)', () => {
154
+ test('Casual Mode enforces Zero Memory Drift: suppresses all memory/directive proposals', async () => {
155
+ const mockMemory = {
156
+ proposeClaim: jest.fn(),
157
+ proposeDirective: jest.fn(),
158
+ addSourceEvent: jest.fn(),
159
+ };
160
+ const mockBrain = {
161
+ generatePlan: jest.fn().mockResolvedValue({
162
+ speech: 'Got it, Alice.',
163
+ language: 'en',
164
+ memoryProposals: [
165
+ { subject: 'actor:alice', predicate: 'mood', value: 'happy' },
166
+ ],
167
+ }),
168
+ };
169
+ const runtime = new runtime_1.SiduriRuntime('comp-casual', { name: 'CasualBot' }, {
170
+ memory: mockMemory,
171
+ brain: mockBrain,
172
+ });
173
+ const casualContext = {
174
+ companionId: 'comp-casual',
175
+ mode: 'casual',
176
+ actor: {
177
+ actorId: 'alice',
178
+ sessionId: 'sess-casual',
179
+ authenticated: true,
180
+ },
181
+ conversation: {
182
+ channel: 'direct',
183
+ correlationId: 'corr-casual-1',
184
+ },
185
+ };
186
+ // User says a teaching statement in Casual mode
187
+ const response = await runtime.processPerception({
188
+ source: 'text_chat',
189
+ text: 'Remember that my name is Alice and I love coffee',
190
+ context: casualContext,
191
+ });
192
+ expect(response.status).toBe('APPROVED');
193
+ expect(response.metadata.mode).toBe('casual');
194
+ // ZERO writes to memory: no source events, no proposed claims
195
+ expect(mockMemory.addSourceEvent).not.toHaveBeenCalled();
196
+ expect(mockMemory.proposeClaim).not.toHaveBeenCalled();
197
+ expect(mockMemory.proposeDirective).not.toHaveBeenCalled();
198
+ expect(response.metadata.proposals).toHaveLength(0);
199
+ expect(response.metadata.memory_proposals).toHaveLength(0);
200
+ });
201
+ test('Teach Mode persists proposals and reflects mode in metadata', async () => {
202
+ const mockMemory = {
203
+ proposeClaim: jest.fn().mockImplementation(async (c) => ({
204
+ id: 'claim-prop-1',
205
+ ...c,
206
+ status: 'PENDING',
207
+ })),
208
+ proposeDirective: jest.fn().mockResolvedValue(undefined),
209
+ addSourceEvent: jest.fn().mockResolvedValue(undefined),
210
+ };
211
+ const mockBrain = {
212
+ generatePlan: jest.fn().mockResolvedValue({
213
+ speech: 'I have recorded your preferred title as Chief Engineer.',
214
+ language: 'en',
215
+ }),
216
+ };
217
+ const runtime = new runtime_1.SiduriRuntime('comp-teach', { name: 'TeachBot' }, {
218
+ memory: mockMemory,
219
+ brain: mockBrain,
220
+ });
221
+ const teachContext = {
222
+ companionId: 'comp-teach',
223
+ mode: 'teach',
224
+ actor: {
225
+ actorId: 'alice',
226
+ sessionId: 'sess-teach',
227
+ authenticated: true,
228
+ },
229
+ conversation: {
230
+ channel: 'direct',
231
+ correlationId: 'corr-teach-1',
232
+ },
233
+ };
234
+ const response = await runtime.processPerception({
235
+ source: 'text_chat',
236
+ text: 'From now on, call me Chief Engineer',
237
+ context: teachContext,
238
+ });
239
+ expect(response.status).toBe('APPROVED');
240
+ expect(response.metadata.mode).toBe('teach');
241
+ expect(mockMemory.proposeClaim).toHaveBeenCalled();
242
+ expect(response.metadata.proposals).toHaveLength(1);
243
+ });
244
+ test('Infers Teach Mode semantically when user uses in-dialogue teaching command', async () => {
245
+ const mockMemory = {
246
+ proposeClaim: jest.fn().mockImplementation(async (c) => ({
247
+ id: 'claim-prop-2',
248
+ ...c,
249
+ status: 'PENDING',
250
+ })),
251
+ proposeDirective: jest.fn().mockResolvedValue(undefined),
252
+ addSourceEvent: jest.fn().mockResolvedValue(undefined),
253
+ };
254
+ const mockBrain = {
255
+ generatePlan: jest.fn().mockResolvedValue({
256
+ speech: 'Recorded the command.',
257
+ language: 'en',
258
+ }),
259
+ };
260
+ const runtime = new runtime_1.SiduriRuntime('comp-infer', { name: 'InferBot' }, {
261
+ memory: mockMemory,
262
+ brain: mockBrain,
263
+ });
264
+ // No explicit mode override in context
265
+ const defaultContext = {
266
+ companionId: 'comp-infer',
267
+ actor: {
268
+ actorId: 'alice',
269
+ sessionId: 'sess-infer',
270
+ authenticated: true,
271
+ },
272
+ conversation: {
273
+ channel: 'direct',
274
+ correlationId: 'corr-infer-1',
275
+ },
276
+ };
277
+ const response = await runtime.processPerception({
278
+ source: 'text_chat',
279
+ text: '!teach remember that your name is Atlas',
280
+ context: defaultContext,
281
+ });
282
+ expect(response.status).toBe('APPROVED');
283
+ expect(response.metadata.mode).toBe('teach');
284
+ expect(mockMemory.proposeClaim).toHaveBeenCalled();
285
+ });
286
+ });
153
287
  });
@@ -92,6 +92,7 @@ const promptCompilationStage = async (context) => {
92
92
  knowledgeData: context.contextRetrieval.knowledgeData,
93
93
  memoryData: context.contextRetrieval.memoryData,
94
94
  lifeContext: context.contextRetrieval.lifeContext,
95
+ effectiveMode: context.intent?.effectiveMode,
95
96
  });
96
97
  context.prompts = prompts;
97
98
  };
@@ -146,6 +147,7 @@ const memorySettlementStage = async (context) => {
146
147
  memory: context.organs.memory,
147
148
  explicitTeaching: context.intent.explicitTeaching,
148
149
  plan: context.plan,
150
+ effectiveMode: context.intent.effectiveMode,
149
151
  });
150
152
  context.memorySettlement = memorySettlement;
151
153
  };
@@ -233,6 +235,7 @@ const envelopeAssemblyStage = async (context) => {
233
235
  subsystemDiagnostics: context.contextRetrieval.subsystemDiagnostics,
234
236
  experienceEvents: context.experienceEmission?.experienceEvents || [],
235
237
  mouthDelivery: context.mouthDelivery,
238
+ effectiveMode: context.intent?.effectiveMode,
236
239
  });
237
240
  context.responseEnvelope = envelope;
238
241
  };
@@ -1,4 +1,4 @@
1
- import { BehaviorOrgan, BehaviorDirective, KnowledgeItem, Claim, RequestContext } from './index';
1
+ import { BehaviorOrgan, BehaviorDirective, KnowledgeItem, Claim, RequestContext, InteractionMode } from './index';
2
2
  export interface PromptCompilationParams {
3
3
  companionName: string;
4
4
  companionId: string;
@@ -10,6 +10,7 @@ export interface PromptCompilationParams {
10
10
  knowledgeData: KnowledgeItem[];
11
11
  memoryData: Claim[];
12
12
  lifeContext?: string[];
13
+ effectiveMode?: InteractionMode;
13
14
  }
14
15
  export interface CompiledPrompts {
15
16
  systemPrompt: string;
@@ -5,7 +5,7 @@ exports.compilePrompts = compilePrompts;
5
5
  * Compiles neutral system prompt and formatted context prompt from structured data.
6
6
  */
7
7
  async function compilePrompts(params) {
8
- const { companionName, companionId, role, requestContext, behavior, activeDirectives, subsystemDiagnostics, knowledgeData, memoryData, lifeContext, } = params;
8
+ const { companionName, companionId, role, requestContext, behavior, activeDirectives, subsystemDiagnostics, knowledgeData, memoryData, lifeContext, effectiveMode, } = params;
9
9
  let contextPrompt = '';
10
10
  if (Object.keys(subsystemDiagnostics).length > 0) {
11
11
  contextPrompt +=
@@ -43,8 +43,14 @@ async function compilePrompts(params) {
43
43
  actorId: requestContext.actor.actorId,
44
44
  })
45
45
  : '';
46
+ const modeInstruction = effectiveMode === 'casual'
47
+ ? 'Operating Mode: Casual (Zero memory drift - do not attempt to persist personal claims or directives).'
48
+ : effectiveMode === 'teach'
49
+ ? 'Operating Mode: Teach Mode (Active learning session - accurately capture user preferences and proposed boundaries for operator review).'
50
+ : undefined;
46
51
  const systemPrompt = [
47
52
  `You are ${companionName}.`,
53
+ modeInstruction,
48
54
  'This is a neutral conversation context.',
49
55
  'Use only approved, permitted memory as factual personal context.',
50
56
  'Do not claim prior personal knowledge when no approved memory supports it.',
@@ -20,7 +20,10 @@ export interface MemoryProposal {
20
20
  }
21
21
  export interface BehaviorProposal {
22
22
  directive: string;
23
- priority: number;
23
+ priority?: number;
24
+ category?: 'guardrail' | 'relational' | 'behavioral' | string;
25
+ scopeActor?: string;
26
+ supersedesId?: string;
24
27
  subject?: string;
25
28
  predicate?: string;
26
29
  value?: string;
@@ -1,4 +1,4 @@
1
- import { StagedResponsePlan, ResponseGateEvaluation, Claim, ResponseCitation, ExperienceEvent, ActionExecutionResult } from './index';
1
+ import { StagedResponsePlan, ResponseGateEvaluation, Claim, ResponseCitation, ExperienceEvent, ActionExecutionResult, InteractionMode } from './index';
2
2
  import { MemoryProposalReceipt } from './memory-settler';
3
3
  import { FormattedMouthOutput } from './mouth-types';
4
4
  export interface AssembleResponseEnvelopeParams {
@@ -14,6 +14,7 @@ export interface AssembleResponseEnvelopeParams {
14
14
  subsystemDiagnostics: Record<string, string>;
15
15
  experienceEvents: ExperienceEvent[];
16
16
  mouthDelivery?: FormattedMouthOutput;
17
+ effectiveMode?: InteractionMode;
17
18
  }
18
19
  /**
19
20
  * Creates the standardized rejection envelope for responses that fail gate evaluation.
@@ -29,7 +29,7 @@ function createGateRejectionEnvelope(stagedPlan, gateEval) {
29
29
  * Assembles the standardized response structure for approved companion responses.
30
30
  */
31
31
  function assembleResponseEnvelope(params) {
32
- const { stagedPlan, speech, language, speechId, createdMemoryProposals, memoryProposalReceipts, actionResults, filteredEvidenceIds, filteredCitations, subsystemDiagnostics, experienceEvents, mouthDelivery, } = params;
32
+ const { stagedPlan, speech, language, speechId, createdMemoryProposals, memoryProposalReceipts, actionResults, filteredEvidenceIds, filteredCitations, subsystemDiagnostics, experienceEvents, mouthDelivery, effectiveMode, } = params;
33
33
  return {
34
34
  status: 'APPROVED',
35
35
  response_id: stagedPlan.responseId,
@@ -42,6 +42,7 @@ function assembleResponseEnvelope(params) {
42
42
  },
43
43
  delivery: mouthDelivery,
44
44
  metadata: {
45
+ mode: effectiveMode ?? 'hybrid',
45
46
  language,
46
47
  proposals: createdMemoryProposals,
47
48
  memory_proposals: memoryProposalReceipts,
@@ -2,33 +2,46 @@ export interface SelfIdentity {
2
2
  companionId: string;
3
3
  name: string;
4
4
  archetype?: string;
5
+ origin?: string;
6
+ ethos?: string;
5
7
  version: string;
6
8
  updatedAt: string;
7
9
  }
8
10
  export interface PersonalityTraits {
9
- warmth: number;
10
- formality: number;
11
- sarcasm: number;
12
- verbosity: number;
13
- curiosity: number;
11
+ warmth?: number;
12
+ formality?: number;
13
+ sarcasm?: number;
14
+ verbosity?: number;
15
+ curiosity?: number;
14
16
  }
15
17
  export interface SelfDirective {
16
18
  id: string;
17
19
  companionId: string;
18
- priority: number;
20
+ priority?: number;
19
21
  directive: string;
20
22
  status: 'PENDING' | 'ACTIVE' | 'DISABLED' | 'SUPERSEDED' | 'REJECTED' | 'REVOKED' | 'EXPIRED';
21
23
  category: 'behavioral' | 'guardrail' | 'relational' | string;
24
+ scopeActor?: string;
22
25
  supersedesId?: string;
23
26
  createdAt?: string;
24
27
  }
25
28
  export interface SelfRelationship {
26
29
  companionId: string;
27
30
  entityId: string;
28
- entityType: 'human' | 'companion' | 'system';
29
- trustScore: number;
30
- familiarity: number;
31
+ entityType?: 'human' | 'companion' | 'system';
32
+ role?: string;
33
+ stance?: string;
34
+ trustScore?: number;
35
+ familiarity?: number;
31
36
  interactionConventions: string[];
37
+ updatedAt?: string;
38
+ }
39
+ export interface SelfDialogueExample {
40
+ id?: string;
41
+ companionId?: string;
42
+ user: string;
43
+ assistant: string;
44
+ createdAt?: string;
32
45
  }
33
46
  export interface LifeInventoryItem {
34
47
  id: string;
@@ -107,7 +120,10 @@ export declare class SiduriDatabase {
107
120
  expireDirective(id: string, companionId?: string): void;
108
121
  disableDirective(id: string, companionId?: string): void;
109
122
  getRelationship(companionId: string, entityId: string): SelfRelationship | undefined;
123
+ getRelationships(companionId: string): SelfRelationship[];
110
124
  upsertRelationship(rel: SelfRelationship): void;
125
+ getExemplars(companionId: string): SelfDialogueExample[];
126
+ setExemplars(companionId: string, exemplars: SelfDialogueExample[]): void;
111
127
  getInventory(companionId: string, domain?: string): LifeInventoryItem[];
112
128
  upsertInventoryItem(item: LifeInventoryItem): void;
113
129
  getFinanceEntries(companionId: string, limit?: number): LifeFinanceEntry[];
package/dist/siduri-db.js CHANGED
@@ -20,6 +20,8 @@ class SiduriDatabase {
20
20
  companion_id TEXT PRIMARY KEY,
21
21
  name TEXT NOT NULL,
22
22
  archetype TEXT,
23
+ origin TEXT,
24
+ ethos TEXT,
23
25
  version TEXT NOT NULL,
24
26
  updated_at TEXT DEFAULT (datetime('now'))
25
27
  );
@@ -41,6 +43,7 @@ class SiduriDatabase {
41
43
  directive TEXT NOT NULL,
42
44
  status TEXT DEFAULT 'ACTIVE',
43
45
  category TEXT DEFAULT 'behavioral',
46
+ scope_actor TEXT,
44
47
  supersedes_id TEXT,
45
48
  created_at TEXT DEFAULT (datetime('now'))
46
49
  );
@@ -48,13 +51,24 @@ class SiduriDatabase {
48
51
  CREATE TABLE IF NOT EXISTS self_relationships (
49
52
  companion_id TEXT NOT NULL,
50
53
  entity_id TEXT NOT NULL,
51
- entity_type TEXT NOT NULL,
54
+ entity_type TEXT NOT NULL DEFAULT 'human',
55
+ role TEXT DEFAULT 'user',
56
+ stance TEXT DEFAULT 'neutral',
52
57
  trust_score REAL DEFAULT 0.5,
53
58
  familiarity REAL DEFAULT 0.5,
54
59
  interaction_conventions TEXT,
60
+ updated_at TEXT DEFAULT (datetime('now')),
55
61
  PRIMARY KEY(companion_id, entity_id)
56
62
  );
57
63
 
64
+ CREATE TABLE IF NOT EXISTS self_exemplars (
65
+ id TEXT PRIMARY KEY,
66
+ companion_id TEXT NOT NULL,
67
+ user_prompt TEXT NOT NULL,
68
+ companion_response TEXT NOT NULL,
69
+ created_at TEXT DEFAULT (datetime('now'))
70
+ );
71
+
58
72
  -- Knowledge Tables
59
73
  CREATE TABLE IF NOT EXISTS life_inventory (
60
74
  id TEXT PRIMARY KEY,
@@ -159,6 +173,42 @@ class SiduriDatabase {
159
173
  catch {
160
174
  // Column already exists
161
175
  }
176
+ try {
177
+ this.db.exec("ALTER TABLE self_identity ADD COLUMN origin TEXT");
178
+ }
179
+ catch {
180
+ // Column already exists
181
+ }
182
+ try {
183
+ this.db.exec("ALTER TABLE self_identity ADD COLUMN ethos TEXT");
184
+ }
185
+ catch {
186
+ // Column already exists
187
+ }
188
+ try {
189
+ this.db.exec("ALTER TABLE self_directives ADD COLUMN scope_actor TEXT");
190
+ }
191
+ catch {
192
+ // Column already exists
193
+ }
194
+ try {
195
+ this.db.exec("ALTER TABLE self_relationships ADD COLUMN role TEXT DEFAULT 'user'");
196
+ }
197
+ catch {
198
+ // Column already exists
199
+ }
200
+ try {
201
+ this.db.exec("ALTER TABLE self_relationships ADD COLUMN stance TEXT DEFAULT 'neutral'");
202
+ }
203
+ catch {
204
+ // Column already exists
205
+ }
206
+ try {
207
+ this.db.exec("ALTER TABLE self_relationships ADD COLUMN updated_at TEXT");
208
+ }
209
+ catch {
210
+ // Column already exists
211
+ }
162
212
  }
163
213
  close() {
164
214
  this.db.close();
@@ -175,21 +225,25 @@ class SiduriDatabase {
175
225
  companionId: row.companion_id,
176
226
  name: row.name,
177
227
  archetype: row.archetype || undefined,
228
+ origin: row.origin || undefined,
229
+ ethos: row.ethos || undefined,
178
230
  version: row.version,
179
231
  updatedAt: row.updated_at
180
232
  };
181
233
  }
182
234
  setIdentity(identity) {
183
235
  const stmt = this.db.prepare(`
184
- INSERT INTO self_identity (companion_id, name, archetype, version, updated_at)
185
- VALUES (?, ?, ?, ?, datetime('now'))
236
+ INSERT INTO self_identity (companion_id, name, archetype, origin, ethos, version, updated_at)
237
+ VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
186
238
  ON CONFLICT(companion_id) DO UPDATE SET
187
239
  name = excluded.name,
188
240
  archetype = excluded.archetype,
241
+ origin = excluded.origin,
242
+ ethos = excluded.ethos,
189
243
  version = excluded.version,
190
244
  updated_at = datetime('now')
191
245
  `);
192
- stmt.run(identity.companionId, identity.name, identity.archetype || null, identity.version);
246
+ stmt.run(identity.companionId, identity.name, identity.archetype || null, identity.origin || null, identity.ethos || null, identity.version);
193
247
  }
194
248
  getPersonality(companionId) {
195
249
  const stmt = this.db.prepare('SELECT * FROM self_personality WHERE companion_id = ?');
@@ -231,16 +285,17 @@ class SiduriDatabase {
231
285
  directive: row.directive,
232
286
  status: row.status,
233
287
  category: row.category,
288
+ scopeActor: row.scope_actor || undefined,
234
289
  supersedesId: row.supersedes_id || undefined,
235
290
  createdAt: row.created_at
236
291
  }));
237
292
  }
238
293
  commitDirective(directive) {
239
294
  const stmt = this.db.prepare(`
240
- INSERT INTO self_directives (id, companion_id, priority, directive, status, category, supersedes_id, created_at)
241
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
295
+ INSERT INTO self_directives (id, companion_id, priority, directive, status, category, scope_actor, supersedes_id, created_at)
296
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
242
297
  `);
243
- stmt.run(directive.id, directive.companionId, directive.priority, directive.directive, directive.status, directive.category, directive.supersedesId || null, directive.createdAt || new Date().toISOString());
298
+ stmt.run(directive.id, directive.companionId, directive.priority !== undefined ? directive.priority : 50, directive.directive, directive.status, directive.category || 'behavioral', directive.scopeActor || null, directive.supersedesId || null, directive.createdAt || new Date().toISOString());
244
299
  }
245
300
  getDirective(id, companionId) {
246
301
  const stmt = companionId
@@ -256,6 +311,7 @@ class SiduriDatabase {
256
311
  directive: row.directive,
257
312
  status: row.status,
258
313
  category: row.category,
314
+ scopeActor: row.scope_actor || undefined,
259
315
  supersedesId: row.supersedes_id || undefined,
260
316
  createdAt: row.created_at,
261
317
  };
@@ -351,23 +407,66 @@ class SiduriDatabase {
351
407
  return {
352
408
  companionId: row.companion_id,
353
409
  entityId: row.entity_id,
354
- entityType: row.entity_type,
355
- trustScore: row.trust_score,
356
- familiarity: row.familiarity,
357
- interactionConventions: row.interaction_conventions ? JSON.parse(row.interaction_conventions) : []
410
+ entityType: row.entity_type || 'human',
411
+ role: row.role || 'user',
412
+ stance: row.stance || 'neutral',
413
+ trustScore: row.trust_score !== undefined && row.trust_score !== null ? row.trust_score : 0.5,
414
+ familiarity: row.familiarity !== undefined && row.familiarity !== null ? row.familiarity : 0.5,
415
+ interactionConventions: row.interaction_conventions ? JSON.parse(row.interaction_conventions) : [],
416
+ updatedAt: row.updated_at || undefined,
358
417
  };
359
418
  }
419
+ getRelationships(companionId) {
420
+ const stmt = this.db.prepare('SELECT * FROM self_relationships WHERE companion_id = ? ORDER BY entity_id ASC');
421
+ return stmt.all(companionId).map((row) => ({
422
+ companionId: row.companion_id,
423
+ entityId: row.entity_id,
424
+ entityType: row.entity_type || 'human',
425
+ role: row.role || 'user',
426
+ stance: row.stance || 'neutral',
427
+ trustScore: row.trust_score !== undefined && row.trust_score !== null ? row.trust_score : 0.5,
428
+ familiarity: row.familiarity !== undefined && row.familiarity !== null ? row.familiarity : 0.5,
429
+ interactionConventions: row.interaction_conventions ? JSON.parse(row.interaction_conventions) : [],
430
+ updatedAt: row.updated_at || undefined,
431
+ }));
432
+ }
360
433
  upsertRelationship(rel) {
361
434
  const stmt = this.db.prepare(`
362
- INSERT INTO self_relationships (companion_id, entity_id, entity_type, trust_score, familiarity, interaction_conventions)
363
- VALUES (?, ?, ?, ?, ?, ?)
435
+ INSERT INTO self_relationships (companion_id, entity_id, entity_type, role, stance, trust_score, familiarity, interaction_conventions, updated_at)
436
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
364
437
  ON CONFLICT(companion_id, entity_id) DO UPDATE SET
365
438
  entity_type = excluded.entity_type,
439
+ role = excluded.role,
440
+ stance = excluded.stance,
366
441
  trust_score = excluded.trust_score,
367
442
  familiarity = excluded.familiarity,
368
- interaction_conventions = excluded.interaction_conventions
443
+ interaction_conventions = excluded.interaction_conventions,
444
+ updated_at = datetime('now')
369
445
  `);
370
- stmt.run(rel.companionId, rel.entityId, rel.entityType, rel.trustScore, rel.familiarity, JSON.stringify(rel.interactionConventions || []));
446
+ stmt.run(rel.companionId, rel.entityId, rel.entityType || 'human', rel.role || 'user', rel.stance || 'neutral', rel.trustScore !== undefined && rel.trustScore !== null ? rel.trustScore : 0.5, rel.familiarity !== undefined && rel.familiarity !== null ? rel.familiarity : 0.5, JSON.stringify(rel.interactionConventions || []));
447
+ }
448
+ getExemplars(companionId) {
449
+ const stmt = this.db.prepare('SELECT * FROM self_exemplars WHERE companion_id = ? ORDER BY created_at ASC');
450
+ return stmt.all(companionId).map((row) => ({
451
+ id: row.id,
452
+ companionId: row.companion_id,
453
+ user: row.user_prompt,
454
+ assistant: row.companion_response,
455
+ createdAt: row.created_at,
456
+ }));
457
+ }
458
+ setExemplars(companionId, exemplars) {
459
+ const delStmt = this.db.prepare('DELETE FROM self_exemplars WHERE companion_id = ?');
460
+ delStmt.run(companionId);
461
+ const insertStmt = this.db.prepare(`
462
+ INSERT INTO self_exemplars (id, companion_id, user_prompt, companion_response, created_at)
463
+ VALUES (?, ?, ?, ?, datetime('now'))
464
+ `);
465
+ for (let i = 0; i < exemplars.length; i++) {
466
+ const ex = exemplars[i];
467
+ const id = ex.id || `ex-${i + 1}-${Date.now()}`;
468
+ insertStmt.run(id, companionId, ex.user, ex.assistant);
469
+ }
371
470
  }
372
471
  // ==========================================
373
472
  // Knowledge / Life DB Methods
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siduri-x/core",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Core runtime types, evidence protocol, action dispatcher, capability validation, and SiduriRuntime protocol",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {