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 +1 -1
- package/bin/cli.js +1 -1
- package/lib/generator.js +67 -20
- package/package.json +1 -1
- package/presets/ai-girlfriend/manifest.json +1 -1
- package/presets/health-butler/manifest.json +1 -1
- package/presets/life-assistant/manifest.json +1 -1
- package/presets/samantha/manifest.json +1 -1
- package/skills/open-persona/SKILL.md +16 -2
- package/templates/skill.template.md +33 -0
- package/templates/soul-injection.template.md +28 -0
package/README.md
CHANGED
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.
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
330
|
-
hasSkillTable:
|
|
331
|
-
skillEntries:
|
|
332
|
-
skillBlocks:
|
|
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.
|
|
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.
|
|
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
|
@@ -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.
|
|
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
|
|