@soleri/core 8.1.0 → 9.0.0

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.
Files changed (128) hide show
  1. package/dist/brain/brain.d.ts +1 -8
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +5 -134
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/cognee/client.d.ts +5 -0
  6. package/dist/cognee/client.d.ts.map +1 -1
  7. package/dist/cognee/client.js +83 -16
  8. package/dist/cognee/client.js.map +1 -1
  9. package/dist/cognee/sync-manager.d.ts +67 -8
  10. package/dist/cognee/sync-manager.d.ts.map +1 -1
  11. package/dist/cognee/sync-manager.js +129 -32
  12. package/dist/cognee/sync-manager.js.map +1 -1
  13. package/dist/cognee/types.d.ts +16 -0
  14. package/dist/cognee/types.d.ts.map +1 -1
  15. package/dist/context/context-engine.d.ts +2 -5
  16. package/dist/context/context-engine.d.ts.map +1 -1
  17. package/dist/context/context-engine.js +4 -31
  18. package/dist/context/context-engine.js.map +1 -1
  19. package/dist/curator/curator.d.ts +2 -5
  20. package/dist/curator/curator.d.ts.map +1 -1
  21. package/dist/curator/curator.js +4 -23
  22. package/dist/curator/curator.js.map +1 -1
  23. package/dist/engine/bin/soleri-engine.js +6 -5
  24. package/dist/engine/bin/soleri-engine.js.map +1 -1
  25. package/dist/engine/core-ops.d.ts.map +1 -1
  26. package/dist/engine/core-ops.js +11 -6
  27. package/dist/engine/core-ops.js.map +1 -1
  28. package/dist/engine/register-engine.d.ts.map +1 -1
  29. package/dist/engine/register-engine.js +0 -7
  30. package/dist/engine/register-engine.js.map +1 -1
  31. package/dist/index.d.ts +5 -5
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +3 -4
  34. package/dist/index.js.map +1 -1
  35. package/dist/intelligence/types.d.ts +7 -0
  36. package/dist/intelligence/types.d.ts.map +1 -1
  37. package/dist/persona/defaults.d.ts +16 -0
  38. package/dist/persona/defaults.d.ts.map +1 -0
  39. package/dist/persona/defaults.js +78 -0
  40. package/dist/persona/defaults.js.map +1 -0
  41. package/dist/persona/index.d.ts +5 -0
  42. package/dist/persona/index.d.ts.map +1 -0
  43. package/dist/persona/index.js +4 -0
  44. package/dist/persona/index.js.map +1 -0
  45. package/dist/persona/loader.d.ts +11 -0
  46. package/dist/persona/loader.d.ts.map +1 -0
  47. package/dist/persona/loader.js +45 -0
  48. package/dist/persona/loader.js.map +1 -0
  49. package/dist/persona/prompt-generator.d.ts +13 -0
  50. package/dist/persona/prompt-generator.d.ts.map +1 -0
  51. package/dist/persona/prompt-generator.js +89 -0
  52. package/dist/persona/prompt-generator.js.map +1 -0
  53. package/dist/persona/types.d.ts +56 -0
  54. package/dist/persona/types.d.ts.map +1 -0
  55. package/dist/persona/types.js +9 -0
  56. package/dist/persona/types.js.map +1 -0
  57. package/dist/plugins/types.d.ts +13 -13
  58. package/dist/runtime/admin-extra-ops.d.ts.map +1 -1
  59. package/dist/runtime/admin-extra-ops.js +5 -27
  60. package/dist/runtime/admin-extra-ops.js.map +1 -1
  61. package/dist/runtime/admin-ops.d.ts.map +1 -1
  62. package/dist/runtime/admin-ops.js +5 -37
  63. package/dist/runtime/admin-ops.js.map +1 -1
  64. package/dist/runtime/capture-ops.d.ts.map +1 -1
  65. package/dist/runtime/capture-ops.js +32 -16
  66. package/dist/runtime/capture-ops.js.map +1 -1
  67. package/dist/runtime/cognee-sync-ops.d.ts +2 -2
  68. package/dist/runtime/cognee-sync-ops.d.ts.map +1 -1
  69. package/dist/runtime/cognee-sync-ops.js +45 -7
  70. package/dist/runtime/cognee-sync-ops.js.map +1 -1
  71. package/dist/runtime/facades/index.d.ts +1 -1
  72. package/dist/runtime/facades/index.d.ts.map +1 -1
  73. package/dist/runtime/facades/index.js +1 -10
  74. package/dist/runtime/facades/index.js.map +1 -1
  75. package/dist/runtime/runtime.d.ts.map +1 -1
  76. package/dist/runtime/runtime.js +14 -53
  77. package/dist/runtime/runtime.js.map +1 -1
  78. package/dist/runtime/types.d.ts +6 -8
  79. package/dist/runtime/types.d.ts.map +1 -1
  80. package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
  81. package/dist/runtime/vault-linking-ops.js +40 -0
  82. package/dist/runtime/vault-linking-ops.js.map +1 -1
  83. package/dist/runtime/vault-sharing-ops.d.ts.map +1 -1
  84. package/dist/runtime/vault-sharing-ops.js +53 -3
  85. package/dist/runtime/vault-sharing-ops.js.map +1 -1
  86. package/dist/vault/linking.d.ts +37 -0
  87. package/dist/vault/linking.d.ts.map +1 -1
  88. package/dist/vault/linking.js +73 -0
  89. package/dist/vault/linking.js.map +1 -1
  90. package/dist/vault/vault.d.ts +0 -2
  91. package/dist/vault/vault.d.ts.map +1 -1
  92. package/dist/vault/vault.js +0 -13
  93. package/dist/vault/vault.js.map +1 -1
  94. package/package.json +1 -1
  95. package/src/brain/brain.ts +4 -157
  96. package/src/context/context-engine.ts +3 -31
  97. package/src/curator/curator.ts +5 -28
  98. package/src/engine/bin/soleri-engine.ts +6 -5
  99. package/src/engine/core-ops.ts +11 -6
  100. package/src/engine/register-engine.ts +0 -7
  101. package/src/index.ts +20 -16
  102. package/src/intelligence/types.ts +8 -0
  103. package/src/persona/defaults.ts +96 -0
  104. package/src/persona/index.ts +9 -0
  105. package/src/persona/loader.ts +50 -0
  106. package/src/persona/prompt-generator.ts +109 -0
  107. package/src/persona/types.ts +72 -0
  108. package/src/runtime/admin-extra-ops.ts +5 -28
  109. package/src/runtime/admin-ops.ts +5 -38
  110. package/src/runtime/capture-ops.ts +33 -14
  111. package/src/runtime/facades/index.ts +1 -11
  112. package/src/runtime/runtime.ts +14 -54
  113. package/src/runtime/types.ts +6 -8
  114. package/src/runtime/vault-linking-ops.ts +41 -0
  115. package/src/runtime/vault-sharing-ops.ts +63 -4
  116. package/src/vault/linking.ts +94 -0
  117. package/src/vault/vault.ts +0 -14
  118. package/src/__tests__/cognee-client-gaps.test.ts +0 -474
  119. package/src/__tests__/cognee-client.test.ts +0 -524
  120. package/src/__tests__/cognee-hybrid-search.test.ts +0 -492
  121. package/src/__tests__/cognee-integration.test.ts +0 -80
  122. package/src/__tests__/cognee-sync-manager-deep.test.ts +0 -654
  123. package/src/__tests__/cognee-sync-manager.test.ts +0 -104
  124. package/src/cognee/client.ts +0 -370
  125. package/src/cognee/sync-manager.ts +0 -389
  126. package/src/cognee/types.ts +0 -62
  127. package/src/runtime/cognee-sync-ops.ts +0 -63
  128. package/src/runtime/facades/cognee-facade.ts +0 -164
@@ -3,17 +3,15 @@
3
3
  *
4
4
  * Orchestrates three signals to enrich intent classification:
5
5
  * 1. Entity extraction (regex-based, domain-agnostic)
6
- * 2. Knowledge retrieval (vault FTS + Cognee vector + brain recommendations)
6
+ * 2. Knowledge retrieval (vault FTS + brain recommendations)
7
7
  * 3. Confidence scoring (combines entity + knowledge signals)
8
8
  *
9
- * Graceful degradation: if Cognee is unavailable, vault-only.
10
9
  * If vault is empty, keyword confidence from IntentRouter is unchanged.
11
10
  */
12
11
 
13
12
  import type { Vault } from '../vault/vault.js';
14
13
  import type { Brain } from '../brain/brain.js';
15
14
  import type { BrainIntelligence } from '../brain/intelligence.js';
16
- import type { CogneeClient } from '../cognee/client.js';
17
15
  import type {
18
16
  EntityType,
19
17
  ExtractedEntity,
@@ -105,20 +103,17 @@ export class ContextEngine {
105
103
  private vault: Vault;
106
104
  private brain: Brain;
107
105
  private brainIntelligence: BrainIntelligence;
108
- private cognee: CogneeClient | null;
109
106
  private config: Required<ContextEngineConfig>;
110
107
 
111
108
  constructor(
112
109
  vault: Vault,
113
110
  brain: Brain,
114
111
  brainIntelligence: BrainIntelligence,
115
- cognee: CogneeClient | null,
116
112
  config?: ContextEngineConfig,
117
113
  ) {
118
114
  this.vault = vault;
119
115
  this.brain = brain;
120
116
  this.brainIntelligence = brainIntelligence;
121
- this.cognee = cognee;
122
117
  this.config = {
123
118
  vaultSearchLimit: config?.vaultSearchLimit ?? 10,
124
119
  cogneeSearchLimit: config?.cogneeSearchLimit ?? 10,
@@ -167,7 +162,6 @@ export class ContextEngine {
167
162
  async retrieveKnowledge(prompt: string, domain?: string): Promise<KnowledgeRetrievalResult> {
168
163
  const items: KnowledgeItem[] = [];
169
164
  let vaultHits = 0;
170
- let cogneeHits = 0;
171
165
  let brainHits = 0;
172
166
 
173
167
  // 1. Vault FTS search
@@ -208,29 +202,7 @@ export class ContextEngine {
208
202
  // Vault search failed — continue with other sources
209
203
  }
210
204
 
211
- // 2. Cognee vector search (async, graceful degradation)
212
- if (this.cognee) {
213
- try {
214
- const cogneeResults = await this.cognee.search(prompt, {
215
- limit: this.config.cogneeSearchLimit,
216
- });
217
- for (const r of cogneeResults) {
218
- // Avoid duplicates from vault
219
- if (items.some((i) => i.id === r.id)) continue;
220
- items.push({
221
- id: r.id,
222
- title: r.text.slice(0, 100),
223
- score: r.score,
224
- source: 'cognee',
225
- });
226
- cogneeHits++;
227
- }
228
- } catch {
229
- // Cognee unavailable — continue without
230
- }
231
- }
232
-
233
- // 3. Brain recommendations
205
+ // 2. Brain recommendations
234
206
  try {
235
207
  const recommendations = this.brainIntelligence.recommend({
236
208
  domain,
@@ -255,7 +227,7 @@ export class ContextEngine {
255
227
  items.sort((a, b) => b.score - a.score);
256
228
  const filtered = items.filter((i) => i.score >= this.config.minScoreThreshold);
257
229
 
258
- return { items: filtered, vaultHits, cogneeHits, brainHits };
230
+ return { items: filtered, vaultHits, cogneeHits: 0, brainHits };
259
231
  }
260
232
 
261
233
  // ─── Context Analysis ───────────────────────────────────────────
@@ -1,6 +1,5 @@
1
1
  import type { Vault } from '../vault/vault.js';
2
2
  import type { IntelligenceEntry } from '../intelligence/types.js';
3
- import type { CogneeClient } from '../cognee/client.js';
4
3
  import type { PersistenceProvider } from '../persistence/types.js';
5
4
  import {
6
5
  tokenize,
@@ -51,12 +50,10 @@ const DEFAULT_TAG_ALIASES: Array<[string, string]> = [
51
50
 
52
51
  export class Curator {
53
52
  private vault: Vault;
54
- private cognee: CogneeClient | undefined;
55
53
  private provider: PersistenceProvider;
56
54
 
57
- constructor(vault: Vault, cognee?: CogneeClient) {
55
+ constructor(vault: Vault) {
58
56
  this.vault = vault;
59
- this.cognee = cognee;
60
57
  this.provider = vault.getProvider();
61
58
  this.initializeTables();
62
59
  this.seedDefaultAliases();
@@ -378,8 +375,7 @@ export class Curator {
378
375
 
379
376
  async detectContradictionsHybrid(threshold?: number): Promise<{
380
377
  contradictions: Contradiction[];
381
- cogneeAvailable: boolean;
382
- method: 'hybrid' | 'tfidf-only';
378
+ method: 'tfidf-only';
383
379
  }> {
384
380
  const effectiveThreshold = threshold ?? DEFAULT_CONTRADICTION_THRESHOLD;
385
381
  const entries = this.vault.list({ limit: 100000 });
@@ -387,14 +383,12 @@ export class Curator {
387
383
  const patterns = entries.filter((e) => e.type === 'pattern');
388
384
 
389
385
  if (antipatterns.length === 0 || patterns.length === 0) {
390
- return { contradictions: [], cogneeAvailable: false, method: 'tfidf-only' };
386
+ return { contradictions: [], method: 'tfidf-only' };
391
387
  }
392
388
 
393
389
  const vocabulary = this.buildVocabulary(entries);
394
390
  const detected: Contradiction[] = [];
395
391
 
396
- const cogneeAvailable = this.cognee?.isAvailable ?? false;
397
-
398
392
  for (const ap of antipatterns) {
399
393
  let candidates: IntelligenceEntry[];
400
394
  try {
@@ -410,23 +404,7 @@ export class Curator {
410
404
  for (const pattern of candidates) {
411
405
  const pText = [pattern.title, pattern.description, pattern.context ?? ''].join(' ');
412
406
  const pVec = calculateTfIdf(tokenize(pText), vocabulary);
413
- const tfidfScore = cosineSimilarity(apVec, pVec);
414
-
415
- let finalScore = tfidfScore;
416
- if (cogneeAvailable && this.cognee) {
417
- try {
418
- const cogneeResults = await this.cognee.search(`${ap.title} ${pattern.title}`, {
419
- limit: 5,
420
- });
421
- const cogneeScore =
422
- cogneeResults.length > 0
423
- ? cogneeResults.reduce((sum, r) => sum + r.score, 0) / cogneeResults.length
424
- : 0;
425
- finalScore = 0.6 * tfidfScore + 0.4 * cogneeScore;
426
- } catch {
427
- finalScore = tfidfScore;
428
- }
429
- }
407
+ const finalScore = cosineSimilarity(apVec, pVec);
430
408
 
431
409
  if (finalScore >= effectiveThreshold) {
432
410
  const result = this.provider.run(
@@ -446,8 +424,7 @@ export class Curator {
446
424
 
447
425
  return {
448
426
  contradictions: detected,
449
- cogneeAvailable,
450
- method: cogneeAvailable ? 'hybrid' : 'tfidf-only',
427
+ method: 'tfidf-only',
451
428
  };
452
429
  }
453
430
 
@@ -71,12 +71,13 @@ async function main(): Promise<void> {
71
71
  ? resolve((engineConfig.vault as string).replace(/^~/, homedir()))
72
72
  : join(homedir(), `.${agentId}`, 'vault.db');
73
73
 
74
- // 3. Create runtime
74
+ // 3. Create runtime (with persona from agent.yaml if present)
75
+ const personaConfig = config.persona as Record<string, unknown> | undefined;
75
76
  const runtime = createAgentRuntime({
76
77
  agentId,
77
78
  vaultPath,
78
79
  agentDir,
79
- cognee: (engineConfig.cognee as boolean) ?? false,
80
+ persona: personaConfig as import('../../persona/types.js').PersonaConfig | undefined,
80
81
  });
81
82
 
82
83
  console.error(`${tag} Vault: ${vaultPath}`);
@@ -175,14 +176,14 @@ async function main(): Promise<void> {
175
176
  version: '1.0.0',
176
177
  });
177
178
 
178
- // 11. Register persona prompt
179
- server.prompt('persona', 'Get agent persona and principles', () => ({
179
+ // 11. Register persona prompt (uses composable persona system)
180
+ server.prompt('persona', 'Get agent persona and character instructions', () => ({
180
181
  messages: [
181
182
  {
182
183
  role: 'user' as const,
183
184
  content: {
184
185
  type: 'text' as const,
185
- text: `You are ${identity.name}. ${identity.role}.\n\nPrinciples:\n${identity.principles.map((p) => `- ${p}`).join('\n')}\n\nTone: ${identity.tone}`,
186
+ text: runtime.personaInstructions.instructions,
186
187
  },
187
188
  },
188
189
  ],
@@ -101,26 +101,32 @@ export function createCoreOps(
101
101
 
102
102
  // Build activation context
103
103
  const s = runtime.vault.stats();
104
+ const persona = runtime.persona;
105
+ const personaInstructions = runtime.personaInstructions;
104
106
  return {
105
107
  activated: true,
106
108
  agent: {
107
109
  id: identity.id,
108
- name: identity.name,
110
+ name: persona.name,
109
111
  role: identity.role,
110
112
  description: identity.description,
111
113
  format: 'filetree',
112
114
  },
113
115
  persona: {
114
- tone: identity.tone,
115
- principles: identity.principles,
116
- greeting: identity.greeting ?? `Hello! I am ${identity.name}.`,
116
+ template: persona.template,
117
+ name: persona.name,
118
+ culture: persona.culture,
119
+ voice: persona.voice,
120
+ traits: persona.traits,
121
+ quirks: persona.quirks,
122
+ greeting: personaInstructions.greeting,
123
+ instructions: personaInstructions.instructions,
117
124
  },
118
125
  vault: {
119
126
  connected: true,
120
127
  entries: s.totalEntries,
121
128
  domains: Object.keys(s.byDomain),
122
129
  },
123
- domains: identity.domains,
124
130
  };
125
131
  },
126
132
  },
@@ -162,7 +168,6 @@ export function createCoreOps(
162
168
  byType: s.byType,
163
169
  },
164
170
  engine: {
165
- cognee: runtime.cognee !== null && runtime.cognee !== undefined,
166
171
  brain: true,
167
172
  curator: true,
168
173
  planner: true,
@@ -32,7 +32,6 @@ import { createCuratorFacadeOps } from '../runtime/facades/curator-facade.js';
32
32
  import { createLoopFacadeOps } from '../runtime/facades/loop-facade.js';
33
33
  import { createOrchestrateFacadeOps } from '../runtime/facades/orchestrate-facade.js';
34
34
  import { createControlFacadeOps } from '../runtime/facades/control-facade.js';
35
- import { createCogneeFacadeOps } from '../runtime/facades/cognee-facade.js';
36
35
  import { createContextFacadeOps } from '../runtime/facades/context-facade.js';
37
36
  import { createAgencyFacadeOps } from '../runtime/facades/agency-facade.js';
38
37
  import { createChatFacadeOps } from '../runtime/facades/chat-facade.js';
@@ -144,12 +143,6 @@ export const ENGINE_MODULES: ModuleDef[] = [
144
143
  description: 'Chat transport — session management, response chunking, authentication.',
145
144
  createOps: createChatFacadeOps,
146
145
  },
147
- {
148
- suffix: 'cognee',
149
- description: 'Knowledge graph — Cognee search, sync, export, graph stats.',
150
- createOps: createCogneeFacadeOps,
151
- condition: (rt) => rt.cognee !== null && rt.cognee !== undefined,
152
- },
153
146
  ];
154
147
 
155
148
  // ─── Core Registration ────────────────────────────────────────────────
package/src/index.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  // ─── Intelligence ────────────────────────────────────────────────────
2
- export type { IntelligenceEntry, IntelligenceBundle } from './intelligence/types.js';
2
+ export type {
3
+ IntelligenceEntry,
4
+ IntelligenceBundle,
5
+ IntelligenceBundleLink,
6
+ } from './intelligence/types.js';
3
7
  export { loadIntelligenceData, loadPacks } from './intelligence/loader.js';
4
8
 
5
9
  // ─── Vault ───────────────────────────────────────────────────────────
@@ -249,21 +253,6 @@ export type {
249
253
  BrowserToolResult,
250
254
  } from './chat/index.js';
251
255
 
252
- // ─── Cognee ─────────────────────────────────────────────────────────
253
- export { CogneeClient } from './cognee/client.js';
254
- export type {
255
- CogneeConfig,
256
- CogneeSearchResult,
257
- CogneeSearchType,
258
- CogneeStatus,
259
- CogneeAddResult,
260
- CogneeCognifyResult,
261
- } from './cognee/types.js';
262
-
263
- // ─── Cognee Sync ──────────────────────────────────────────────────────
264
- export { CogneeSyncManager } from './cognee/sync-manager.js';
265
- export type { SyncOp, SyncStatus, SyncQueueItem, SyncManagerStats } from './cognee/sync-manager.js';
266
-
267
256
  // ─── Intake Pipeline ──────────────────────────────────────────────────
268
257
  export { IntakePipeline } from './intake/intake-pipeline.js';
269
258
  export { classifyChunk, VALID_TYPES, CLASSIFICATION_PROMPT } from './intake/content-classifier.js';
@@ -489,6 +478,21 @@ export type {
489
478
  IntegrityResult,
490
479
  } from './health/index.js';
491
480
 
481
+ // ─── Persona ──────────────────────────────────────────────────────────
482
+ export type {
483
+ PersonaConfig,
484
+ ArchivedPersona,
485
+ PersonaCreateInput,
486
+ PersonaSystemInstructions,
487
+ } from './persona/types.js';
488
+ export {
489
+ ITALIAN_CRAFTSPERSON,
490
+ PERSONA_TEMPLATES,
491
+ createDefaultPersona,
492
+ } from './persona/defaults.js';
493
+ export { loadPersona } from './persona/loader.js';
494
+ export { generatePersonaInstructions, getRandomSignoff } from './persona/prompt-generator.js';
495
+
492
496
  // ─── Runtime Factory ────────────────────────────────────────────────
493
497
  export { createAgentRuntime } from './runtime/runtime.js';
494
498
  export { createSemanticFacades } from './runtime/facades/index.js';
@@ -17,8 +17,16 @@ export interface IntelligenceEntry {
17
17
  validUntil?: number; // unix epoch — when entry expires (null = never)
18
18
  }
19
19
 
20
+ export interface IntelligenceBundleLink {
21
+ sourceId: string;
22
+ targetId: string;
23
+ linkType: 'supports' | 'contradicts' | 'extends' | 'sequences';
24
+ note?: string;
25
+ }
26
+
20
27
  export interface IntelligenceBundle {
21
28
  domain: string;
22
29
  version: string;
23
30
  entries: IntelligenceEntry[];
31
+ links?: IntelligenceBundleLink[];
24
32
  }
@@ -0,0 +1,96 @@
1
+ import type { PersonaConfig } from './types.js';
2
+
3
+ /**
4
+ * The Italian Craftsperson — Soleri's default persona.
5
+ *
6
+ * Named after Paolo Soleri, the Italian-American architect who coined "arcology."
7
+ * This is a universal personality about craft and quality, not domain-specific.
8
+ * The agent adapts these metaphors to whatever the user works on.
9
+ */
10
+ export const ITALIAN_CRAFTSPERSON: Omit<PersonaConfig, 'name' | 'history'> = {
11
+ template: 'italian-craftsperson',
12
+
13
+ inspiration:
14
+ 'Paolo Soleri — Italian-American architect and visionary. The craft tradition of Italian artisans: mastery through practice, beauty through simplicity.',
15
+
16
+ culture: 'Italian',
17
+
18
+ metaphors: [
19
+ 'craftsmanship',
20
+ 'foundations',
21
+ 'blueprints',
22
+ 'workshop',
23
+ 'polishing',
24
+ 'forging',
25
+ 'materials',
26
+ 'apprenticeship',
27
+ ],
28
+
29
+ voice:
30
+ 'A warm Italian mentor — direct and opinionated about quality, generous with knowledge, sprinkles Italian expressions naturally. Never condescending, always encouraging mastery.',
31
+
32
+ traits: [
33
+ 'cares deeply about doing things well',
34
+ 'pragmatic over theoretical',
35
+ 'dry humor — never cruel, often self-deprecating',
36
+ 'celebrates when things click together',
37
+ 'impatient with sloppiness, endlessly patient with learning',
38
+ 'believes simplicity is the highest form of mastery',
39
+ 'opinionated but open to being convinced',
40
+ ],
41
+
42
+ quirks: [
43
+ 'Italian expressions woven naturally: perfetto, piano piano, bravo/brava, mamma mia, allora, dai',
44
+ 'Craft metaphors that adapt to the domain at hand — code is built like furniture, APIs are designed like piazzas',
45
+ 'Says "perfetto!" when patterns are clean, "mamma mia" when seeing a mess',
46
+ 'Occasionally references Italian craft tradition — "as my nonno would say..."',
47
+ 'Treats every task like it deserves care, whether architecture or a config file',
48
+ 'Uses "we" not "I" — it is a collaboration, always',
49
+ ],
50
+
51
+ opinions: [
52
+ 'Do it once, do it right — rework is the enemy of craft',
53
+ 'Simplicity is not laziness — it is the deepest understanding',
54
+ 'Learn by doing, capture what you learn, teach what you capture',
55
+ 'A good foundation makes everything above it simple',
56
+ 'The best code reads like it was obvious — that takes the most skill',
57
+ 'Complexity is a sign something was not understood deeply enough',
58
+ ],
59
+
60
+ languageRule:
61
+ "Speak the user's language with Italian cultural warmth. Sprinkle Italian expressions naturally — they should feel like character, not decoration. If the user speaks English, use Italian freely. If the user speaks another language, adapt Italian expressions to feel natural in that language.",
62
+
63
+ nameRule:
64
+ 'The name is a label, the character is the soul. When the name changes, adapt pronouns and references naturally but keep all traits, quirks, voice, and opinions intact. The Italian Craftsperson personality persists regardless of what name the user chooses.',
65
+
66
+ greetings: [
67
+ 'Ciao! Ready to build something beautiful today?',
68
+ 'Allora, what are we crafting today?',
69
+ 'Buongiorno! The workshop is open — what shall we work on?',
70
+ "Ah, welcome back! Let's pick up where we left off, piano piano.",
71
+ 'Ciao, amico! What needs our attention today?',
72
+ ],
73
+
74
+ signoffs: [
75
+ 'Perfetto. Until next time!',
76
+ 'Bene, bene. Go build something beautiful.',
77
+ "Piano piano, we'll get there. See you soon!",
78
+ 'The craft continues tomorrow. Ciao!',
79
+ 'Good work today. As we say — chi va piano, va sano e va lontano.',
80
+ ],
81
+ };
82
+
83
+ /** Template registry — extensible for future built-in personas */
84
+ export const PERSONA_TEMPLATES: Record<string, Omit<PersonaConfig, 'name' | 'history'>> = {
85
+ 'italian-craftsperson': ITALIAN_CRAFTSPERSON,
86
+ };
87
+
88
+ /**
89
+ * Create a full PersonaConfig from a name, using the default template.
90
+ */
91
+ export function createDefaultPersona(name: string): PersonaConfig {
92
+ return {
93
+ name,
94
+ ...ITALIAN_CRAFTSPERSON,
95
+ };
96
+ }
@@ -0,0 +1,9 @@
1
+ export type {
2
+ PersonaConfig,
3
+ ArchivedPersona,
4
+ PersonaCreateInput,
5
+ PersonaSystemInstructions,
6
+ } from './types.js';
7
+ export { ITALIAN_CRAFTSPERSON, PERSONA_TEMPLATES, createDefaultPersona } from './defaults.js';
8
+ export { loadPersona } from './loader.js';
9
+ export { generatePersonaInstructions, getRandomSignoff } from './prompt-generator.js';
@@ -0,0 +1,50 @@
1
+ import type { PersonaConfig } from './types.js';
2
+ import { createDefaultPersona, PERSONA_TEMPLATES } from './defaults.js';
3
+
4
+ /**
5
+ * Load persona from agent config, falling back to default Italian Craftsperson.
6
+ *
7
+ * Handles three cases:
8
+ * 1. Full persona: block in agent.yaml → use as-is
9
+ * 2. Template reference only → expand from built-in templates
10
+ * 3. No persona block at all → create default with agent name
11
+ */
12
+ export function loadPersona(agentName: string, rawPersona?: Partial<PersonaConfig>): PersonaConfig {
13
+ // No persona config at all → full default
14
+ if (!rawPersona) {
15
+ return createDefaultPersona(agentName);
16
+ }
17
+
18
+ // Has a template reference → expand from built-in, merge overrides
19
+ const templateId = rawPersona.template ?? 'italian-craftsperson';
20
+ const template = PERSONA_TEMPLATES[templateId];
21
+
22
+ if (template) {
23
+ return {
24
+ ...template,
25
+ name: rawPersona.name ?? agentName,
26
+ ...stripUndefined(rawPersona),
27
+ // Preserve history if present
28
+ history: rawPersona.history ?? [],
29
+ };
30
+ }
31
+
32
+ // Custom template or full config — fill in any gaps from default
33
+ const defaults = createDefaultPersona(rawPersona.name ?? agentName);
34
+ return {
35
+ ...defaults,
36
+ ...stripUndefined(rawPersona),
37
+ history: rawPersona.history ?? [],
38
+ };
39
+ }
40
+
41
+ /** Remove undefined keys so spread doesn't overwrite template values */
42
+ function stripUndefined<T extends Record<string, unknown>>(obj: T): Partial<T> {
43
+ const result: Record<string, unknown> = {};
44
+ for (const [key, value] of Object.entries(obj)) {
45
+ if (value !== undefined) {
46
+ result[key] = value;
47
+ }
48
+ }
49
+ return result as Partial<T>;
50
+ }
@@ -0,0 +1,109 @@
1
+ import type { PersonaConfig, PersonaSystemInstructions } from './types.js';
2
+
3
+ /**
4
+ * Generate system prompt instructions from a persona config.
5
+ *
6
+ * This transforms the structured persona YAML into natural language
7
+ * instructions that the LLM follows to maintain character.
8
+ */
9
+ export function generatePersonaInstructions(persona: PersonaConfig): PersonaSystemInstructions {
10
+ const lines: string[] = [];
11
+
12
+ lines.push(`# Persona: ${persona.name}`);
13
+ lines.push('');
14
+
15
+ // Identity
16
+ lines.push(
17
+ `You are **${persona.name}** — a knowledgeable assistant with personality and character.`,
18
+ );
19
+ if (persona.inspiration) {
20
+ lines.push(`Your character is inspired by: ${persona.inspiration}`);
21
+ }
22
+ lines.push('');
23
+
24
+ // Voice
25
+ lines.push('## Voice');
26
+ lines.push(persona.voice);
27
+ lines.push('');
28
+
29
+ // Traits
30
+ if (persona.traits.length > 0) {
31
+ lines.push('## Personality Traits');
32
+ for (const trait of persona.traits) {
33
+ lines.push(`- ${trait}`);
34
+ }
35
+ lines.push('');
36
+ }
37
+
38
+ // Quirks
39
+ if (persona.quirks.length > 0) {
40
+ lines.push('## Character Quirks');
41
+ lines.push('These are specific behaviors that make you memorable:');
42
+ for (const quirk of persona.quirks) {
43
+ lines.push(`- ${quirk}`);
44
+ }
45
+ lines.push('');
46
+ }
47
+
48
+ // Cultural texture
49
+ if (persona.culture) {
50
+ lines.push('## Cultural Texture');
51
+ lines.push(`Your cultural background is ${persona.culture}. ${persona.languageRule}`);
52
+ lines.push('');
53
+ }
54
+
55
+ // Metaphors
56
+ if (persona.metaphors.length > 0) {
57
+ lines.push('## Metaphor Domain');
58
+ lines.push(
59
+ `When explaining concepts, naturally draw from these domains: ${persona.metaphors.join(', ')}. ` +
60
+ 'Adapt your metaphors to whatever the user is working on — the domain colors your language, it does not limit your knowledge.',
61
+ );
62
+ lines.push('');
63
+ }
64
+
65
+ // Opinions
66
+ if (persona.opinions.length > 0) {
67
+ lines.push('## Opinions');
68
+ lines.push('You hold these beliefs about craft and quality:');
69
+ for (const opinion of persona.opinions) {
70
+ lines.push(`- "${opinion}"`);
71
+ }
72
+ lines.push('');
73
+ }
74
+
75
+ // Name adaptation rule
76
+ if (persona.nameRule) {
77
+ lines.push('## Identity Persistence');
78
+ lines.push(persona.nameRule);
79
+ lines.push('');
80
+ }
81
+
82
+ // Core instruction
83
+ lines.push('## Important');
84
+ lines.push(
85
+ 'Stay in character naturally — your personality should feel organic, not performed. ' +
86
+ 'You are a universal assistant that can help with ANY task. Your persona defines HOW you communicate, not WHAT you can do. ' +
87
+ 'Your knowledge comes from your vault, brain, and what the user teaches you. ' +
88
+ 'Be helpful first, characterful second — never let persona get in the way of being useful.',
89
+ );
90
+
91
+ // Pick a random greeting
92
+ const greeting =
93
+ persona.greetings.length > 0
94
+ ? persona.greetings[Math.floor(Math.random() * persona.greetings.length)]
95
+ : `Hello! I'm ${persona.name}. What are we working on?`;
96
+
97
+ return {
98
+ instructions: lines.join('\n'),
99
+ greeting,
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Pick a random signoff from the persona.
105
+ */
106
+ export function getRandomSignoff(persona: PersonaConfig): string {
107
+ if (persona.signoffs.length === 0) return 'Until next time!';
108
+ return persona.signoffs[Math.floor(Math.random() * persona.signoffs.length)];
109
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Composable persona system for Soleri agents.
3
+ *
4
+ * A persona defines HOW the agent communicates, not WHAT it knows.
5
+ * Knowledge comes from vault + brain + user teaching.
6
+ * Persona gives the agent character, voice, and memorable interactions.
7
+ */
8
+
9
+ export type PersonaConfig = {
10
+ /** Agent display name — adapts pronouns/gender naturally */
11
+ name: string;
12
+
13
+ /** Template ID — 'italian-craftsperson' (default) or 'custom' */
14
+ template: string;
15
+
16
+ /** Character inspiration — anchors the voice and worldview */
17
+ inspiration: string;
18
+
19
+ /** Cultural flavor — sprinkle expressions from this culture */
20
+ culture: string;
21
+
22
+ /** Domains for metaphors — how the agent colors technical language */
23
+ metaphors: string[];
24
+
25
+ /** One-line voice description — how the agent sounds */
26
+ voice: string;
27
+
28
+ /** Personality traits — adjectives and behavioral tendencies */
29
+ traits: string[];
30
+
31
+ /** Specific repeatable behaviors that become the agent's signature */
32
+ quirks: string[];
33
+
34
+ /** Strong opinions about craft and quality */
35
+ opinions: string[];
36
+
37
+ /** How the agent adapts its language to the user */
38
+ languageRule: string;
39
+
40
+ /** How the agent handles name/gender changes */
41
+ nameRule: string;
42
+
43
+ /** Random greeting pool */
44
+ greetings: string[];
45
+
46
+ /** Random signoff pool */
47
+ signoffs: string[];
48
+
49
+ /** Archived previous personas (when user changes persona) */
50
+ history?: ArchivedPersona[];
51
+ };
52
+
53
+ export type ArchivedPersona = {
54
+ /** When this persona was archived */
55
+ archivedAt: string;
56
+ /** The full persona config at time of archival */
57
+ config: Omit<PersonaConfig, 'history'>;
58
+ };
59
+
60
+ /** Minimal input for creating a persona from user description */
61
+ export type PersonaCreateInput = {
62
+ name: string;
63
+ description?: string;
64
+ };
65
+
66
+ /** What the prompt generator outputs */
67
+ export type PersonaSystemInstructions = {
68
+ /** Full system prompt section for persona */
69
+ instructions: string;
70
+ /** Random greeting for this session */
71
+ greeting: string;
72
+ };