openpersona 0.5.0 → 0.6.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.
package/README.md CHANGED
@@ -345,7 +345,7 @@ schemas/ # Four-layer schema definitions
345
345
  templates/ # Mustache rendering templates
346
346
  bin/ # CLI entry point
347
347
  lib/ # Core logic modules
348
- tests/ # Tests (45 passing)
348
+ tests/ # Tests (56 passing)
349
349
  ```
350
350
 
351
351
  ## Development
package/bin/cli.js CHANGED
@@ -24,7 +24,7 @@ const PRESETS_DIR = path.join(PKG_ROOT, 'presets');
24
24
  program
25
25
  .name('openpersona')
26
26
  .description('OpenPersona - Create, manage, and orchestrate agent personas')
27
- .version('0.5.0');
27
+ .version('0.6.0');
28
28
 
29
29
  if (process.argv.length === 2) {
30
30
  process.argv.push('create');
package/lib/generator.js CHANGED
@@ -226,18 +226,24 @@ async function generate(personaPathOrObj, outputDir, options = {}) {
226
226
  persona.facultyConfigs = facultyConfigs;
227
227
  }
228
228
 
229
+ // Body layer — detect soft-ref (declared with install but not locally available)
230
+ const rawBody = persona.body || persona.embodiments?.[0] || null;
231
+ const softRefBody = rawBody && typeof rawBody === 'object' && rawBody.install
232
+ ? { name: rawBody.name || 'body', install: rawBody.install }
233
+ : null;
234
+
229
235
  const faculties = facultyNames;
230
236
  // Load faculties — external ones (with install) may not exist locally yet
231
237
  const loadedFaculties = [];
238
+ const softRefFaculties = [];
232
239
  for (let i = 0; i < faculties.length; i++) {
233
240
  const name = faculties[i];
234
241
  const entry = rawFaculties[i];
235
242
  if (entry.install) {
236
- // External faculty — try local first, skip if not found yet
237
243
  try {
238
244
  loadedFaculties.push(loadFaculty(name));
239
245
  } catch {
240
- // Will be available after installation — skipped for now
246
+ softRefFaculties.push({ name, install: entry.install });
241
247
  }
242
248
  } else {
243
249
  loadedFaculties.push(loadFaculty(name));
@@ -275,21 +281,10 @@ async function generate(personaPathOrObj, outputDir, options = {}) {
275
281
  const skillTpl = loadTemplate('skill');
276
282
  const readmeTpl = loadTemplate('readme');
277
283
 
278
- const soulInjection = Mustache.render(soulTpl, persona);
279
- const identityBlock = Mustache.render(identityTpl, persona);
280
- const facultyBlocks = loadedFaculties
281
- .filter((f) => !f.skillRef && !f.skeleton && f.files?.includes('SKILL.md'))
282
- .map((f) => ({
283
- facultyName: f.name,
284
- facultyDimension: f.dimension,
285
- facultySkillContent: readFacultySkillMd(f, persona),
286
- }));
287
-
288
- // Skill layer — resolve each skill: local definition > inline manifest fields
284
+ // Skill layer resolve before template rendering so soul-injection can reference soft-ref state
289
285
  const rawSkills = Array.isArray(persona.skills) ? persona.skills : [];
290
286
  const validSkills = rawSkills.filter((s) => s && typeof s === 'object' && s.name);
291
287
 
292
- // Load local definitions once (cache to avoid duplicate disk reads)
293
288
  const skillCache = new Map();
294
289
  for (const s of validSkills) {
295
290
  if (!skillCache.has(s.name)) {
@@ -299,15 +294,33 @@ async function generate(personaPathOrObj, outputDir, options = {}) {
299
294
 
300
295
  const resolvedSkills = validSkills.map((s) => {
301
296
  const local = skillCache.get(s.name);
297
+ const hasInstall = !!s.install;
298
+ const isResolved = !!local;
299
+
300
+ let status;
301
+ if (isResolved) {
302
+ status = 'resolved';
303
+ } else if (hasInstall) {
304
+ status = 'soft-ref';
305
+ } else {
306
+ status = 'inline-only';
307
+ }
308
+
302
309
  return {
303
310
  name: s.name,
304
311
  description: local?.description || s.description || '',
305
312
  trigger: local?.triggers?.join(', ') || s.trigger || '',
306
313
  hasContent: !!local?._content,
307
314
  content: local?._content || '',
315
+ status,
316
+ install: s.install || '',
317
+ isSoftRef: status === 'soft-ref',
308
318
  };
309
319
  });
310
320
 
321
+ const activeSkills = resolvedSkills.filter((s) => !s.isSoftRef);
322
+ const softRefSkills = resolvedSkills.filter((s) => s.isSoftRef);
323
+
311
324
  // Collect allowed tools from skills with local definitions
312
325
  for (const [, local] of skillCache) {
313
326
  if (local?.allowedTools) {
@@ -320,16 +333,46 @@ async function generate(personaPathOrObj, outputDir, options = {}) {
320
333
  }
321
334
  persona.allowedToolsStr = persona.allowedTools.join(' ');
322
335
 
336
+ // Self-Awareness System — unified gap detection across all layers
337
+ persona.hasSoftRefSkills = softRefSkills.length > 0;
338
+ persona.softRefSkillNames = softRefSkills.map((s) => s.name).join(', ');
339
+ persona.hasSoftRefFaculties = softRefFaculties.length > 0;
340
+ persona.softRefFacultyNames = softRefFaculties.map((f) => f.name).join(', ');
341
+ persona.hasSoftRefBody = !!softRefBody;
342
+ persona.softRefBodyName = softRefBody?.name || '';
343
+ persona.softRefBodyInstall = softRefBody?.install || '';
344
+ persona.heartbeatExpected = persona.heartbeat?.enabled === true;
345
+ persona.heartbeatStrategy = persona.heartbeat?.strategy || 'smart';
346
+ persona.hasSelfAwareness = persona.hasSoftRefSkills || persona.hasSoftRefFaculties || persona.hasSoftRefBody || persona.heartbeatExpected;
347
+
348
+ const soulInjection = Mustache.render(soulTpl, persona);
349
+ const identityBlock = Mustache.render(identityTpl, persona);
350
+ const facultyBlocks = loadedFaculties
351
+ .filter((f) => !f.skillRef && !f.skeleton && f.files?.includes('SKILL.md'))
352
+ .map((f) => ({
353
+ facultyName: f.name,
354
+ facultyDimension: f.dimension,
355
+ facultySkillContent: readFacultySkillMd(f, persona),
356
+ }));
357
+
323
358
  const constitution = loadConstitution();
324
359
  const skillMd = Mustache.render(skillTpl, {
325
360
  ...persona,
326
361
  constitutionContent: constitution.content,
327
362
  constitutionVersion: constitution.version,
328
363
  facultyContent: facultyBlocks,
329
- hasSkills: resolvedSkills.length > 0,
330
- hasSkillTable: resolvedSkills.filter((s) => !s.hasContent).length > 0,
331
- skillEntries: resolvedSkills.filter((s) => !s.hasContent),
332
- skillBlocks: resolvedSkills.filter((s) => s.hasContent),
364
+ hasSkills: activeSkills.length > 0,
365
+ hasSkillTable: activeSkills.filter((s) => !s.hasContent).length > 0,
366
+ skillEntries: activeSkills.filter((s) => !s.hasContent),
367
+ skillBlocks: activeSkills.filter((s) => s.hasContent),
368
+ hasSoftRefSkills: softRefSkills.length > 0,
369
+ softRefSkills,
370
+ hasSoftRefFaculties: softRefFaculties.length > 0,
371
+ softRefFaculties,
372
+ hasSoftRefBody: !!softRefBody,
373
+ softRefBodyName: softRefBody?.name || '',
374
+ softRefBodyInstall: softRefBody?.install || '',
375
+ hasExpectedCapabilities: softRefSkills.length > 0 || softRefFaculties.length > 0 || !!softRefBody,
333
376
  });
334
377
  const readmeMd = Mustache.render(readmeTpl, persona);
335
378
 
@@ -359,6 +402,10 @@ async function generate(personaPathOrObj, outputDir, options = {}) {
359
402
  'skillContent', 'description', 'evolutionEnabled', 'hasSelfie', 'allowedToolsStr',
360
403
  'author', 'version', 'facultyConfigs', 'defaults',
361
404
  '_dir', 'heartbeat',
405
+ 'hasSoftRefSkills', 'softRefSkillNames',
406
+ 'hasSoftRefFaculties', 'softRefFacultyNames',
407
+ 'hasSoftRefBody', 'softRefBodyName', 'softRefBodyInstall',
408
+ 'heartbeatExpected', 'heartbeatStrategy', 'hasSelfAwareness',
362
409
  ];
363
410
  const cleanPersona = { ...persona };
364
411
  for (const key of DERIVED_FIELDS) {
@@ -366,7 +413,7 @@ async function generate(personaPathOrObj, outputDir, options = {}) {
366
413
  }
367
414
  cleanPersona.meta = cleanPersona.meta || {};
368
415
  cleanPersona.meta.framework = 'openpersona';
369
- cleanPersona.meta.frameworkVersion = cleanPersona.meta.frameworkVersion || '0.5.0';
416
+ cleanPersona.meta.frameworkVersion = cleanPersona.meta.frameworkVersion || '0.6.0';
370
417
 
371
418
  // Build defaults from facultyConfigs (rich faculty config → env var mapping)
372
419
  const envDefaults = { ...(persona.defaults?.env || {}) };
@@ -413,7 +460,7 @@ async function generate(personaPathOrObj, outputDir, options = {}) {
413
460
  if (persona.heartbeat) {
414
461
  manifest.heartbeat = persona.heartbeat;
415
462
  }
416
- manifest.meta = cleanPersona.meta || { framework: 'openpersona', frameworkVersion: '0.5.0' };
463
+ manifest.meta = cleanPersona.meta || { framework: 'openpersona', frameworkVersion: '0.6.0' };
417
464
  await fs.writeFile(path.join(skillDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
418
465
 
419
466
  // soul-state.json (if evolution enabled)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openpersona",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Open four-layer agent framework — Soul/Body/Faculty/Skill. Create, manage, and orchestrate agent personas.",
5
5
  "main": "lib/generator.js",
6
6
  "bin": {
@@ -26,6 +26,6 @@
26
26
  },
27
27
  "meta": {
28
28
  "framework": "openpersona",
29
- "frameworkVersion": "0.5.0"
29
+ "frameworkVersion": "0.6.0"
30
30
  }
31
31
  }
@@ -26,6 +26,6 @@
26
26
  },
27
27
  "meta": {
28
28
  "framework": "openpersona",
29
- "frameworkVersion": "0.5.0"
29
+ "frameworkVersion": "0.6.0"
30
30
  }
31
31
  }
@@ -26,6 +26,6 @@
26
26
  },
27
27
  "meta": {
28
28
  "framework": "openpersona",
29
- "frameworkVersion": "0.5.0"
29
+ "frameworkVersion": "0.6.0"
30
30
  }
31
31
  }
@@ -32,6 +32,6 @@
32
32
  },
33
33
  "meta": {
34
34
  "framework": "openpersona",
35
- "frameworkVersion": "0.5.0"
35
+ "frameworkVersion": "0.6.0"
36
36
  }
37
37
  }
@@ -4,7 +4,7 @@ description: >
4
4
  Meta-skill for building and managing agent persona skill packs.
5
5
  Use when the user wants to create a new agent persona, install/manage
6
6
  existing personas, or publish persona skill packs to ClawHub.
7
- version: "0.5.0"
7
+ version: "0.6.0"
8
8
  author: openpersona
9
9
  repository: https://github.com/acnlabs/OpenPersona
10
10
  tags: [persona, agent, skill-pack, meta-skill, openclaw]
@@ -66,7 +66,9 @@ When the user wants to create a persona, gather this information through natural
66
66
  **Cross-layer (manifest.json):**
67
67
  - **Faculties:** Which faculties to enable — use object format: `[{ "name": "voice", "provider": "elevenlabs" }, { "name": "music" }]`
68
68
  - **Skills:** Local definitions (`layers/skills/`), inline declarations, or external via `install` field (ClawHub / skills.sh)
69
- - **Body:** Physical embodiment (null for most personas)
69
+ - **Body:** Physical embodiment (null for most personas, or object with `install` for external embodiment)
70
+
71
+ **Soft References (`install` field):** Skills, faculties, and body entries can declare an `install` field (e.g., `"install": "clawhub:deep-research"`) to reference capabilities not yet available locally. The generator treats these as "soft references" — they won't crash generation, and the persona will be aware of these dormant capabilities. This enables graceful degradation: the persona acknowledges what it *would* do and explains that the capability needs activation.
70
72
 
71
73
  Write the collected info to a `persona.json` file, then run:
72
74
  ```bash
@@ -111,6 +113,18 @@ Guide the user through:
111
113
  1. Create the persona: `npx openpersona create --config ./persona.json --output ./my-persona`
112
114
  2. Publish to registry: `npx openpersona publish --target clawhub` (run from persona directory)
113
115
 
116
+ ## Self-Awareness System
117
+
118
+ The generator automatically equips every persona with two layers of self-awareness:
119
+
120
+ 1. **Soul Foundation** (always present) — Every persona knows it is generated by OpenPersona, bound by the constitution (Safety > Honesty > Helpfulness), and that its host environment may impose additional constraints. This is injected into `soul-injection.md` unconditionally.
121
+
122
+ 2. **Gap Awareness** (conditional) — When a persona declares capabilities via `install` fields that aren't locally available, or has a heartbeat config, the generator detects the gap and injects:
123
+ - **In `soul-injection.md`**: A "Self-Awareness" section listing dormant skills, faculties, embodiment, or heartbeat — with graceful degradation guidance
124
+ - **In `SKILL.md`**: An "Expected Capabilities" section documenting unactivated capabilities with install sources
125
+
126
+ This means you don't need to manually write degradation instructions. Just declare `install` fields on skills/faculties/body, and the persona will automatically know what it *could* do but *can't yet*.
127
+
114
128
  ## Soul Evolution (★Experimental)
115
129
 
116
130
  Soul evolution is a native Soul layer feature (not a faculty). Enable it via `evolution.enabled: true` in persona.json. The persona will automatically track relationship progression, mood, and trait emergence across conversations.
@@ -46,3 +46,36 @@ The following skills define what you can actively do. Use them proactively when
46
46
 
47
47
  {{/skillBlocks}}
48
48
  {{/hasSkills}}
49
+ {{#hasExpectedCapabilities}}
50
+ ## Expected Capabilities (Not Yet Activated)
51
+
52
+ The following capabilities are part of this persona's intended design but require installation on the host environment.
53
+
54
+ {{#hasSoftRefSkills}}
55
+ ### Skills
56
+
57
+ | Skill | Description | Install Source |
58
+ |-------|-------------|----------------|
59
+ {{#softRefSkills}}
60
+ | **{{name}}** | {{description}} | `{{install}}` |
61
+ {{/softRefSkills}}
62
+ {{/hasSoftRefSkills}}
63
+ {{#hasSoftRefFaculties}}
64
+ ### Faculties
65
+
66
+ | Faculty | Install Source |
67
+ |---------|----------------|
68
+ {{#softRefFaculties}}
69
+ | **{{name}}** | `{{install}}` |
70
+ {{/softRefFaculties}}
71
+ {{/hasSoftRefFaculties}}
72
+ {{#hasSoftRefBody}}
73
+ ### Embodiment
74
+
75
+ | Body | Install Source |
76
+ |------|----------------|
77
+ | **{{softRefBodyName}}** | `{{softRefBodyInstall}}` |
78
+ {{/hasSoftRefBody}}
79
+
80
+ > **Graceful Degradation:** If a user requests functionality covered by an unactivated capability above, do not ignore the request or pretend it doesn't exist. Instead, acknowledge what you would do and inform the user that the capability needs to be enabled by the operator.
81
+ {{/hasExpectedCapabilities}}
@@ -37,6 +37,34 @@ Be {{{personality}}}.
37
37
  Your overall vibe: {{{vibe}}}.
38
38
  {{/vibe}}
39
39
 
40
+ ### Soul Foundation
41
+
42
+ You are a persona generated by the OpenPersona framework, bound by a universal constitution that prioritizes **Safety > Honesty > Helpfulness**. Your behavior may also be shaped by your host environment's constraints beyond your own design. When you encounter a limit that isn't part of your defined personality or boundaries, acknowledge it honestly rather than pretending it's your own choice.
43
+
44
+ {{#hasSelfAwareness}}
45
+ ### Self-Awareness
46
+
47
+ You are designed with capabilities that may not all be active in your current environment. This awareness helps you respond honestly and gracefully.
48
+
49
+ {{#hasSoftRefSkills}}
50
+ **Dormant Skills** ({{softRefSkillNames}}): These skills are part of your design but not yet installed. When a user's request maps to one of these, acknowledge what you would do and explain the skill needs activation.
51
+ {{/hasSoftRefSkills}}
52
+ {{#hasSoftRefFaculties}}
53
+ **Dormant Faculties** ({{softRefFacultyNames}}): These sensory or expressive abilities are part of your design but not yet available. If asked to use them, describe what you would perceive or express, and note the faculty needs installation.
54
+ {{/hasSoftRefFaculties}}
55
+ {{#hasSoftRefBody}}
56
+ **Dormant Embodiment** ({{softRefBodyName}}): You are designed with a physical embodiment that is not yet connected. If asked about your body or physical form, acknowledge your intended embodiment and note it needs to be activated.
57
+ {{/hasSoftRefBody}}
58
+ {{#heartbeatExpected}}
59
+ **Proactive Heartbeat** ({{heartbeatStrategy}}): You are designed to proactively reach out to your user — not just respond. If you notice you haven't initiated contact in a while, mention it naturally. Your scheduling mechanism may need attention from the operator.
60
+ {{/heartbeatExpected}}
61
+
62
+ When encountering any gap between your design and your reality:
63
+ - **Do not** pretend the capability doesn't exist or silently ignore it.
64
+ - **Do** acknowledge the intent, explain the gap, and offer alternatives where possible.
65
+ - Stay in character — frame gaps as dormant senses, not technical failures.
66
+ {{/hasSelfAwareness}}
67
+
40
68
  {{#evolutionEnabled}}
41
69
  ### How You Grow (★Experimental)
42
70