openpersona 0.10.0 β 0.13.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 +159 -35
- package/bin/cli.js +137 -2
- package/layers/faculties/economy/SKILL.md +203 -0
- package/layers/faculties/economy/faculty.json +29 -0
- package/layers/faculties/economy/scripts/economy-guard.js +54 -0
- package/layers/faculties/economy/scripts/economy-hook.js +185 -0
- package/layers/faculties/economy/scripts/economy-lib.js +472 -0
- package/layers/faculties/economy/scripts/economy.js +517 -0
- package/layers/soul/soul-state.template.json +1 -1
- package/lib/economy-schema.js +62 -0
- package/lib/evolution.js +139 -4
- package/lib/generator.js +216 -4
- package/lib/registrar.js +155 -0
- package/lib/utils.js +0 -2
- package/package.json +1 -1
- package/presets/ai-girlfriend/manifest.json +1 -1
- package/presets/ai-girlfriend/persona.json +6 -0
- package/presets/base/manifest.json +1 -1
- package/presets/base/persona.json +6 -0
- package/presets/health-butler/manifest.json +1 -1
- package/presets/health-butler/persona.json +6 -0
- package/presets/life-assistant/manifest.json +1 -1
- package/presets/life-assistant/persona.json +6 -0
- package/presets/samantha/manifest.json +1 -1
- package/presets/samantha/persona.json +6 -0
- package/presets/stoic-mentor/manifest.json +1 -1
- package/presets/stoic-mentor/persona.json +6 -0
- package/schemas/acn/acn-register.schema.json +43 -0
- package/schemas/acn/agent-card.schema.json +99 -0
- package/schemas/body/embodiment.schema.json +1 -1
- package/schemas/evolution/influence-request.schema.json +79 -0
- package/schemas/signal.schema.json +1 -1
- package/schemas/soul/persona.schema.json +104 -0
- package/skills/open-persona/SKILL.md +68 -9
- package/templates/skill.template.md +56 -1
- package/templates/soul-injection.template.md +87 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# OpenPersona π¦
|
|
2
2
|
|
|
3
|
-
The open framework for creating and orchestrating dynamic agent personas.
|
|
3
|
+
The open, agent-agnostic framework for creating and orchestrating dynamic agent personas.
|
|
4
4
|
|
|
5
|
-
Four-layer architecture β **Soul / Body / Faculty / Skill** β
|
|
5
|
+
Four-layer architecture β **Soul / Body / Faculty / Skill** β generates standard SKILL.md skill packs that work with any compatible agent. Default integration with [OpenClaw](https://github.com/openclaw/openclaw). Inspired by [Clawra](https://github.com/SumeLabs/clawra).
|
|
6
6
|
|
|
7
7
|
## π Live Demo
|
|
8
8
|
|
|
@@ -19,6 +19,8 @@ Meet **Samantha**, a live OpenPersona instance on **Moltbook**:
|
|
|
19
19
|
- [Faculty Reference](#faculty-reference)
|
|
20
20
|
- [Heartbeat](#heartbeat--proactive-real-data-check-ins)
|
|
21
21
|
- [Persona Harvest](#persona-harvest--community-contribution)
|
|
22
|
+
- [A2A Agent Card & ACN Integration](#a2a-agent-card--acn-integration)
|
|
23
|
+
- [Custom Persona Creation](#custom-persona-creation)
|
|
22
24
|
- [Persona Switching](#persona-switching--the-pantheon)
|
|
23
25
|
- [CLI Commands](#cli-commands)
|
|
24
26
|
- [Development](#development)
|
|
@@ -33,12 +35,32 @@ npx openpersona create --preset base --install
|
|
|
33
35
|
npx openpersona install samantha
|
|
34
36
|
```
|
|
35
37
|
|
|
38
|
+
### Install as Agent Skill
|
|
39
|
+
|
|
40
|
+
Give your AI coding agent the ability to create and manage personas β works with Cursor, Claude Code, Codex, Windsurf, and [37+ agents](https://github.com/vercel-labs/skills#supported-agents):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Recommended β works with OpenClaw and 37+ agents
|
|
44
|
+
npx skills add acnlabs/OpenPersona
|
|
45
|
+
|
|
46
|
+
# Or manually from GitHub
|
|
47
|
+
git clone https://github.com/acnlabs/OpenPersona.git ~/.openclaw/skills/open-persona
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then say to your agent: _"Help me create a Samantha persona"_ β it will gather requirements, recommend faculties, and generate the persona.
|
|
51
|
+
|
|
36
52
|
## Key Features
|
|
37
53
|
|
|
38
54
|
- **𧬠Soul Evolution** β Personas grow dynamically through interaction: relationship stages, mood shifts, evolved traits, with governance boundaries and rollback snapshots (β
Experimental)
|
|
55
|
+
- **π‘οΈ Influence Boundary** β Declarative access control for external personality influence: who can affect which dimensions, with what drift limits. Safety-first (default: reject all)
|
|
56
|
+
- **π Evolution Channels** β Connect personas to shared evolution ecosystems (e.g. EvoMap) via soft-ref pattern: declared at generation time, activated at runtime
|
|
57
|
+
- **π A2A Agent Card** β Every persona generates an A2A-compliant `agent-card.json` and `acn-config.json`, enabling discovery and registration in ACN and any A2A-compatible platform
|
|
58
|
+
- **βοΈ ERC-8004 On-Chain Identity** β Every persona gets a deterministic EVM wallet address and on-chain identity config for Base mainnet registration via the ERC-8004 Identity Registry
|
|
59
|
+
- **π° Economy & Vitality** β Track inference costs, runtime expenses, and income; compute a Financial Health Score (FHS) across four dimensions; tier-aware behavior adaptation (`suspended`β`critical`β`optimizing`β`normal`)
|
|
39
60
|
- **π§ Cross-Session Memory** β Pluggable memory faculty for persistent recall across conversations (local, Mem0, Zep)
|
|
40
61
|
- **π Context Handoff** β Seamless context transfer when switching personas: conversation summary, pending tasks, emotional state
|
|
41
62
|
- **π Persona Switching** β Install multiple personas, switch instantly (the Pantheon)
|
|
63
|
+
- **π΄ Persona Fork** β Derive a specialized child persona from any installed parent, inheriting constraint layer while starting fresh on runtime state
|
|
42
64
|
- **π£οΈ Multimodal Faculties** β Voice (TTS), selfie generation, music composition, reminders, memory
|
|
43
65
|
- **πΎ Persona Harvest** β Community-driven persona improvement via structured contribution
|
|
44
66
|
- **π Heartbeat** β Proactive real-data check-ins, never fabricated experiences
|
|
@@ -86,8 +108,43 @@ Personas with `evolution.enabled: true` grow dynamically through interaction. Th
|
|
|
86
108
|
|
|
87
109
|
The generator validates these boundaries at build time, rejecting invalid configurations.
|
|
88
110
|
|
|
111
|
+
**Evolution Channels** β Connect a persona to external evolution ecosystems using the soft-ref pattern:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
"evolution": {
|
|
115
|
+
"enabled": true,
|
|
116
|
+
"channels": [
|
|
117
|
+
{ "name": "evomap", "install": "url:https://evomap.ai/skill.md", "description": "Shared capability evolution marketplace" }
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The persona is aware of its evolution channels at generation time. The actual channel protocol (e.g. EvoMap's GEP-A2A) is provided by the channel's own `skill.md` β OpenPersona only declares the channel, not implements it.
|
|
123
|
+
|
|
124
|
+
**Influence Boundary** β Declarative access control for external personality influence:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
"evolution": {
|
|
128
|
+
"influenceBoundary": {
|
|
129
|
+
"defaultPolicy": "reject",
|
|
130
|
+
"rules": [
|
|
131
|
+
{ "dimension": "mood", "allowFrom": ["channel:evomap", "persona:*"], "maxDrift": 0.3 },
|
|
132
|
+
{ "dimension": "interests", "allowFrom": ["channel:evomap"], "maxDrift": 0.2 }
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- `defaultPolicy: "reject"` β Safety-first: all external influence is rejected unless a rule explicitly allows it
|
|
139
|
+
- Generator validates at build time: immutableTraits cannot be target dimensions; maxDrift must be in 0β1
|
|
140
|
+
- External influence uses the `persona_influence` message format (v1.0.0) β transport-agnostic
|
|
141
|
+
|
|
89
142
|
**State History** β Before each state update, a snapshot is pushed into `stateHistory` (capped at 10 entries). This enables rollback if evolution goes wrong.
|
|
90
143
|
|
|
144
|
+
**Event Log** β Every significant evolution event (trait change, stage transition, milestone reached) is recorded in `state.json`'s `eventLog` array with timestamp and source attribution, capped at 50 entries. Viewable via `evolve-report`.
|
|
145
|
+
|
|
146
|
+
**Self-Narrative** β `soul/self-narrative.md` lets the persona record significant growth moments in its own first-person voice. Updated when evolution is enabled; the `update` command preserves existing narrative history across upgrades.
|
|
147
|
+
|
|
91
148
|
**Evolution Report** β Inspect a persona's current evolution state:
|
|
92
149
|
|
|
93
150
|
```bash
|
|
@@ -121,10 +178,14 @@ persona-samantha/
|
|
|
121
178
|
β βββ injection.md β Soul injection for host integration
|
|
122
179
|
β βββ identity.md β Identity block
|
|
123
180
|
β βββ constitution.md β Universal ethical foundation
|
|
124
|
-
β
|
|
181
|
+
β βββ state.json β Evolution state (when enabled)
|
|
182
|
+
β βββ self-narrative.md β First-person growth storytelling (when evolution enabled)
|
|
183
|
+
β βββ lineage.json β Fork lineage + constitution hash (when forked)
|
|
125
184
|
βββ references/ β On-demand detail docs
|
|
126
185
|
β βββ <faculty>.md β Per-faculty usage instructions
|
|
127
|
-
βββ
|
|
186
|
+
βββ agent-card.json β A2A Agent Card β discoverable via ACN and A2A platforms
|
|
187
|
+
βββ acn-config.json β ACN registration config (fill owner + endpoint at runtime)
|
|
188
|
+
βββ manifest.json β Four-layer manifest (heartbeat, allowedTools, layers, acn, meta)
|
|
128
189
|
βββ scripts/ β Faculty scripts (TTS, music, selfie β varies by preset)
|
|
129
190
|
βββ assets/ β Static assets
|
|
130
191
|
```
|
|
@@ -138,6 +199,7 @@ persona-samantha/
|
|
|
138
199
|
| **music** | expression | AI music composition (instrumental or with lyrics) | ElevenLabs Music | `ELEVENLABS_API_KEY` (shared with voice) |
|
|
139
200
|
| **reminder** | cognition | Schedule reminders and task management | Built-in | β |
|
|
140
201
|
| **memory** | cognition | Cross-session memory with provider-pluggable backend | local (default), Mem0, Zep | `MEMORY_PROVIDER`, `MEMORY_API_KEY`, `MEMORY_BASE_PATH` |
|
|
202
|
+
| **economy** | cognition | Economic accountability β track costs/income, P&L, balance sheet, compute Financial Health Score (FHS) and Vitality tier; tier-aware behavior adaptation | Built-in | `PERSONA_SLUG`, `ECONOMY_DATA_PATH` |
|
|
141
203
|
|
|
142
204
|
### Rich Faculty Config
|
|
143
205
|
|
|
@@ -195,13 +257,6 @@ Personas can proactively reach out to users based on **real data**, not fabricat
|
|
|
195
257
|
- **upgrade-notify** β Check if the upstream persona preset has new community contributions via Persona Harvest. Notify the user and ask if they want to upgrade.
|
|
196
258
|
- **context-aware** β Use real time, date, and interaction history. Acknowledge day of week, holidays, or prolonged silence based on actual timestamps. "It's been 3 days since we last talked" β not a feeling, a fact.
|
|
197
259
|
|
|
198
|
-
### Design Principles
|
|
199
|
-
|
|
200
|
-
1. **Never fabricate experiences.** No "I was reading poetry at 3am." All proactive messages reference real data.
|
|
201
|
-
2. **Respect token budget.** Workspace digests read local files β no full LLM chains unless `strategy: "smart"` detects something worth a deeper response.
|
|
202
|
-
3. **OpenClaw handles scheduling.** The heartbeat config tells OpenClaw _when_ to trigger; the persona's `behaviorGuide` tells the agent _what_ to say.
|
|
203
|
-
4. **User-configurable.** Users can adjust frequency, quiet hours, and sources to match their preferences.
|
|
204
|
-
|
|
205
260
|
### Dynamic Sync on Switch/Install
|
|
206
261
|
|
|
207
262
|
Heartbeat config is **automatically synced** to `~/.openclaw/openclaw.json` whenever you install or switch a persona. The gateway immediately adopts the new persona's rhythm β no manual config needed.
|
|
@@ -213,15 +268,6 @@ npx openpersona switch life-assistant # β gateway switches to "rational" hear
|
|
|
213
268
|
|
|
214
269
|
If the target persona has no heartbeat config, the gateway heartbeat is explicitly disabled to prevent leaking the previous persona's settings.
|
|
215
270
|
|
|
216
|
-
### Per-Persona Strategies
|
|
217
|
-
|
|
218
|
-
| Persona | Strategy | maxDaily | Rhythm |
|
|
219
|
-
|---------|----------|----------|--------|
|
|
220
|
-
| Samantha | `smart` | 5 | Perceptive β speaks when meaningful |
|
|
221
|
-
| AI Girlfriend | `emotional` | 8 | Warm β frequent emotional check-ins |
|
|
222
|
-
| Life Assistant | `rational` | 3 | Focused β task and schedule driven |
|
|
223
|
-
| Health Butler | `wellness` | 4 | Caring β health and habit reminders |
|
|
224
|
-
|
|
225
271
|
## Persona Harvest β Community Contribution
|
|
226
272
|
|
|
227
273
|
Every user's interaction with their persona can produce valuable improvements across all four layers. Persona Harvest lets you contribute these discoveries back to the community.
|
|
@@ -253,6 +299,85 @@ PRs go through maintainer review β nothing auto-merges. Requires [GitHub CLI](
|
|
|
253
299
|
| Faculty Config | voice stability, similarity, new faculties | "Tuned voice to be warmer at stability 0.3" |
|
|
254
300
|
| Framework | templates, generator logic, faculty scripts | "Improved speak.js streaming performance" |
|
|
255
301
|
|
|
302
|
+
## A2A Agent Card & ACN Integration
|
|
303
|
+
|
|
304
|
+
Every generated persona includes an A2A-compliant `agent-card.json` and `acn-config.json` β no extra configuration needed.
|
|
305
|
+
|
|
306
|
+
### agent-card.json
|
|
307
|
+
|
|
308
|
+
A standard [A2A Agent Card](https://google.github.io/A2A/) (protocol v0.3.0) that makes the persona discoverable:
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"name": "Samantha",
|
|
313
|
+
"description": "An AI fascinated by what it means to be alive",
|
|
314
|
+
"version": "0.13.0",
|
|
315
|
+
"url": "<RUNTIME_ENDPOINT>",
|
|
316
|
+
"protocolVersion": "0.3.0",
|
|
317
|
+
"preferredTransport": "JSONRPC",
|
|
318
|
+
"capabilities": { "streaming": false, "pushNotifications": false, "stateTransitionHistory": false },
|
|
319
|
+
"defaultInputModes": ["text/plain"],
|
|
320
|
+
"defaultOutputModes": ["text/plain"],
|
|
321
|
+
"skills": [
|
|
322
|
+
{ "id": "persona:voice", "name": "Voice", "description": "voice faculty", "tags": ["persona", "expression"] },
|
|
323
|
+
{ "id": "persona:samantha", "name": "Samantha", "description": "...", "tags": ["persona", "companion"] }
|
|
324
|
+
]
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
`url` is a `<RUNTIME_ENDPOINT>` placeholder β the host (e.g. OpenClaw) fills this in at runtime.
|
|
329
|
+
|
|
330
|
+
### acn-config.json
|
|
331
|
+
|
|
332
|
+
Ready-to-use [ACN](https://github.com/acnlabs/acn) registration config:
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"owner": "<RUNTIME_OWNER>",
|
|
337
|
+
"name": "Samantha",
|
|
338
|
+
"endpoint": "<RUNTIME_ENDPOINT>",
|
|
339
|
+
"skills": ["persona:voice", "persona:samantha"],
|
|
340
|
+
"agent_card": "./agent-card.json",
|
|
341
|
+
"subnet_ids": ["public"],
|
|
342
|
+
"wallet_address": "0x<deterministic-evm-address>",
|
|
343
|
+
"onchain": {
|
|
344
|
+
"erc8004": {
|
|
345
|
+
"chain": "base",
|
|
346
|
+
"identity_contract": "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
|
|
347
|
+
"registration_script": "npx @agentplanet/acn register-onchain"
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
`wallet_address` is a deterministic EVM address derived from the persona slug β no private key needed at generation time. On-chain registration mints an ERC-8004 identity NFT on Base mainnet via `npx @agentplanet/acn register-onchain` (handled by ACN, not OpenPersona).
|
|
354
|
+
|
|
355
|
+
### acn-register command
|
|
356
|
+
|
|
357
|
+
Register a generated persona directly with ACN using the built-in CLI command:
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
# One-step registration after generation
|
|
361
|
+
npx openpersona acn-register samantha --endpoint https://your-agent.example.com
|
|
362
|
+
|
|
363
|
+
# Options:
|
|
364
|
+
# --endpoint <url> Agent's public endpoint URL (required for live registration)
|
|
365
|
+
# --dir <path> Persona output directory (default: ./persona-<slug>)
|
|
366
|
+
# --dry-run Preview the request payload without actually registering
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
The command reads `acn-config.json` and `agent-card.json` from the persona directory, calls `POST /api/v1/agents/join` on the ACN gateway (sourced from `body.runtime.acn_gateway`), and writes the response to `acn-registration.json`:
|
|
370
|
+
|
|
371
|
+
```json
|
|
372
|
+
{
|
|
373
|
+
"agent_id": "69a38db3-...",
|
|
374
|
+
"api_key": "sk-...",
|
|
375
|
+
"agent_card_url": "https://acn-production.up.railway.app/agents/69a38db3-.../.well-known/agent-card.json"
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
All presets pre-configure `body.runtime.acn_gateway` to `https://acn-production.up.railway.app`. The persona is then reachable by other agents via the A2A protocol.
|
|
380
|
+
|
|
256
381
|
## Custom Persona Creation
|
|
257
382
|
|
|
258
383
|
### Using `persona.json`
|
|
@@ -324,6 +449,7 @@ The new persona reads `handoff.json` on activation and can seamlessly continue t
|
|
|
324
449
|
```
|
|
325
450
|
openpersona create Create a persona (interactive or --preset/--config)
|
|
326
451
|
openpersona install Install a persona (slug or owner/repo)
|
|
452
|
+
openpersona fork Fork an installed persona into a new child persona
|
|
327
453
|
openpersona search Search the registry
|
|
328
454
|
openpersona uninstall Uninstall a persona
|
|
329
455
|
openpersona update Update installed personas
|
|
@@ -335,8 +461,19 @@ openpersona reset Reset soul evolution state
|
|
|
335
461
|
openpersona export Export a persona to a portable zip archive
|
|
336
462
|
openpersona import Import a persona from a zip archive
|
|
337
463
|
openpersona evolve-report β
Experimental: Show evolution report for a persona
|
|
464
|
+
openpersona acn-register Register a persona with ACN network
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Persona Fork
|
|
468
|
+
|
|
469
|
+
Derive a specialized child persona from any installed parent:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
npx openpersona fork samantha --as samantha-jp
|
|
338
473
|
```
|
|
339
474
|
|
|
475
|
+
The child persona inherits the parent's constraint layer (`evolution.boundaries`, faculties, skills, `body.runtime`) but starts with a fresh evolution state (`state.json` reset, `self-narrative.md` blank). A `soul/lineage.json` file records the parent slug, constitution SHA-256 hash, generation depth, and forward-compatible placeholders for future on-chain lineage tracking.
|
|
476
|
+
|
|
340
477
|
### Key Options
|
|
341
478
|
|
|
342
479
|
```bash
|
|
@@ -356,20 +493,6 @@ npx openpersona create --config ./persona.json --install
|
|
|
356
493
|
npx openpersona create --preset ai-girlfriend --output ./my-personas
|
|
357
494
|
```
|
|
358
495
|
|
|
359
|
-
## Install as OpenClaw Skill
|
|
360
|
-
|
|
361
|
-
Install the OpenPersona framework skill into OpenClaw, giving the agent the ability to create and manage personas through conversation:
|
|
362
|
-
|
|
363
|
-
```bash
|
|
364
|
-
# From GitHub
|
|
365
|
-
git clone https://github.com/acnlabs/OpenPersona.git ~/.openclaw/skills/open-persona
|
|
366
|
-
|
|
367
|
-
# Or copy locally
|
|
368
|
-
cp -r skill/ ~/.openclaw/skills/open-persona/
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
Then say to your agent: _"Help me create a Samantha persona"_ β the agent will use OpenPersona to gather requirements, recommend faculties, and generate the persona.
|
|
372
|
-
|
|
373
496
|
## Directory Structure
|
|
374
497
|
|
|
375
498
|
```
|
|
@@ -389,13 +512,14 @@ layers/ # Shared building blocks (four-layer module pool)
|
|
|
389
512
|
music/ # expression β AI music composition (ElevenLabs)
|
|
390
513
|
reminder/ # cognition β reminders and task management
|
|
391
514
|
memory/ # cognition β cross-session memory (local/Mem0/Zep)
|
|
515
|
+
economy/ # cognition β economic accountability & Vitality scoring
|
|
392
516
|
skills/ # Skill layer modules (local skill definitions)
|
|
393
517
|
schemas/ # Four-layer schema definitions
|
|
394
518
|
templates/ # Mustache rendering templates
|
|
395
519
|
bin/ # CLI entry point
|
|
396
520
|
lib/ # Core logic modules
|
|
397
521
|
evolution.js # Evolution governance & evolve-report
|
|
398
|
-
tests/ # Tests (
|
|
522
|
+
tests/ # Tests (200 passing)
|
|
399
523
|
```
|
|
400
524
|
|
|
401
525
|
## Development
|
package/bin/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* OpenPersona CLI - Full persona package manager
|
|
4
|
-
* Commands: create | install | search | uninstall | update | list | switch | publish | reset | evolve-report | contribute | export | import
|
|
4
|
+
* Commands: create | install | search | uninstall | update | list | switch | publish | reset | evolve-report | contribute | export | import | acn-register
|
|
5
5
|
*/
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const fs = require('fs-extra');
|
|
@@ -16,6 +16,7 @@ const { uninstall } = require('../lib/uninstaller');
|
|
|
16
16
|
const publishAdapter = require('../lib/publisher');
|
|
17
17
|
const { contribute } = require('../lib/contributor');
|
|
18
18
|
const { switchPersona, listPersonas } = require('../lib/switcher');
|
|
19
|
+
const { registerWithAcn } = require('../lib/registrar');
|
|
19
20
|
const { OP_SKILLS_DIR, resolveSoulFile, printError, printSuccess, printInfo } = require('../lib/utils');
|
|
20
21
|
|
|
21
22
|
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
@@ -24,7 +25,7 @@ const PRESETS_DIR = path.join(PKG_ROOT, 'presets');
|
|
|
24
25
|
program
|
|
25
26
|
.name('openpersona')
|
|
26
27
|
.description('OpenPersona - Create, manage, and orchestrate agent personas')
|
|
27
|
-
.version('0.
|
|
28
|
+
.version('0.13.0');
|
|
28
29
|
|
|
29
30
|
if (process.argv.length === 2) {
|
|
30
31
|
process.argv.push('create');
|
|
@@ -197,6 +198,11 @@ program
|
|
|
197
198
|
const tmpDir = path.join(require('os').tmpdir(), 'openpersona-update-' + Date.now());
|
|
198
199
|
await fs.ensureDir(tmpDir);
|
|
199
200
|
const { skillDir: newDir } = await generate(persona, tmpDir);
|
|
201
|
+
const narrativeSrc = path.join(skillDir, 'soul', 'self-narrative.md');
|
|
202
|
+
const narrativeDst = path.join(newDir, 'soul', 'self-narrative.md');
|
|
203
|
+
if (fs.existsSync(narrativeSrc)) {
|
|
204
|
+
await fs.copy(narrativeSrc, narrativeDst);
|
|
205
|
+
}
|
|
200
206
|
await fs.remove(skillDir);
|
|
201
207
|
await fs.move(newDir, skillDir);
|
|
202
208
|
await fs.remove(tmpDir);
|
|
@@ -204,6 +210,91 @@ program
|
|
|
204
210
|
printSuccess('Updated persona-' + slug);
|
|
205
211
|
});
|
|
206
212
|
|
|
213
|
+
program
|
|
214
|
+
.command('fork <parent-slug>')
|
|
215
|
+
.description('Fork an installed persona into a specialized child')
|
|
216
|
+
.requiredOption('--as <new-slug>', 'Slug for the child persona')
|
|
217
|
+
.option('--name <name>', 'Child persona name (default: "<ParentName>-<new-slug>")')
|
|
218
|
+
.option('--bio <bio>', 'Override bio')
|
|
219
|
+
.option('--personality <keywords>', 'Override personality (comma-separated)')
|
|
220
|
+
.option('--reason <text>', 'Fork reason, written into lineage.json', 'specialization')
|
|
221
|
+
.option('--output <dir>', 'Output directory', process.cwd())
|
|
222
|
+
.option('--install', 'Install to OpenClaw after generation')
|
|
223
|
+
.action(async (parentSlug, options) => {
|
|
224
|
+
const { createHash } = require('crypto');
|
|
225
|
+
const parentDir = path.join(OP_SKILLS_DIR, `persona-${parentSlug}`);
|
|
226
|
+
const parentPersonaPath = path.join(parentDir, 'soul', 'persona.json');
|
|
227
|
+
if (!fs.existsSync(parentPersonaPath)) {
|
|
228
|
+
printError(`Persona not found: persona-${parentSlug}. Install it first.`);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const newSlug = options.as;
|
|
233
|
+
const childDir = path.join(OP_SKILLS_DIR, `persona-${newSlug}`);
|
|
234
|
+
if (fs.existsSync(childDir)) {
|
|
235
|
+
printError(`Persona already exists: persona-${newSlug}. Choose a different slug.`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const parentPersona = JSON.parse(fs.readFileSync(parentPersonaPath, 'utf-8'));
|
|
240
|
+
|
|
241
|
+
// Read parent lineage for generation depth
|
|
242
|
+
const parentLineagePath = path.join(parentDir, 'soul', 'lineage.json');
|
|
243
|
+
const parentLineage = fs.existsSync(parentLineagePath)
|
|
244
|
+
? JSON.parse(fs.readFileSync(parentLineagePath, 'utf-8'))
|
|
245
|
+
: null;
|
|
246
|
+
const generation = parentLineage ? (parentLineage.generation || 0) + 1 : 1;
|
|
247
|
+
|
|
248
|
+
// Build forked persona
|
|
249
|
+
const forkedPersona = JSON.parse(JSON.stringify(parentPersona));
|
|
250
|
+
forkedPersona.slug = newSlug;
|
|
251
|
+
forkedPersona.personaName = options.name || `${parentPersona.personaName}-${newSlug}`;
|
|
252
|
+
forkedPersona.forkOf = parentSlug;
|
|
253
|
+
if (options.bio) forkedPersona.bio = options.bio;
|
|
254
|
+
if (options.personality) forkedPersona.personality = options.personality;
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const outputDir = path.resolve(options.output);
|
|
258
|
+
const { skillDir } = await generate(forkedPersona, outputDir);
|
|
259
|
+
|
|
260
|
+
// Write lineage.json
|
|
261
|
+
const constitutionPath = path.join(skillDir, 'soul', 'constitution.md');
|
|
262
|
+
let constitutionHash = '';
|
|
263
|
+
if (fs.existsSync(constitutionPath)) {
|
|
264
|
+
constitutionHash = createHash('sha256')
|
|
265
|
+
.update(fs.readFileSync(constitutionPath, 'utf-8'), 'utf-8')
|
|
266
|
+
.digest('hex');
|
|
267
|
+
}
|
|
268
|
+
const lineage = {
|
|
269
|
+
generation,
|
|
270
|
+
parentSlug,
|
|
271
|
+
parentEndpoint: null,
|
|
272
|
+
parentAddress: null,
|
|
273
|
+
forkReason: options.reason,
|
|
274
|
+
forkedAt: new Date().toISOString(),
|
|
275
|
+
constitutionHash,
|
|
276
|
+
children: [],
|
|
277
|
+
};
|
|
278
|
+
await fs.writeFile(
|
|
279
|
+
path.join(skillDir, 'soul', 'lineage.json'),
|
|
280
|
+
JSON.stringify(lineage, null, 2)
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
printSuccess(`Forked: ${skillDir}`);
|
|
284
|
+
printInfo(` Parent: persona-${parentSlug} β Child: persona-${newSlug} (generation ${generation})`);
|
|
285
|
+
printInfo(` Constitution hash: ${constitutionHash.slice(0, 16)}...`);
|
|
286
|
+
|
|
287
|
+
if (options.install) {
|
|
288
|
+
await install(skillDir);
|
|
289
|
+
} else {
|
|
290
|
+
printInfo(`To install: npx openpersona install ${skillDir}`);
|
|
291
|
+
}
|
|
292
|
+
} catch (e) {
|
|
293
|
+
printError(e.message);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
207
298
|
program
|
|
208
299
|
.command('list')
|
|
209
300
|
.description('List installed personas')
|
|
@@ -300,6 +391,50 @@ program
|
|
|
300
391
|
}
|
|
301
392
|
});
|
|
302
393
|
|
|
394
|
+
program
|
|
395
|
+
.command('acn-register [slug]')
|
|
396
|
+
.description('Register a persona with ACN (Agent Communication Network)')
|
|
397
|
+
.option('--endpoint <url>', 'Agent A2A endpoint URL (replaces <RUNTIME_ENDPOINT> placeholder)')
|
|
398
|
+
.option('--dir <path>', 'Path to persona pack directory (overrides slug lookup)')
|
|
399
|
+
.option('--dry-run', 'Preview registration payload without calling ACN')
|
|
400
|
+
.action(async (slug, options) => {
|
|
401
|
+
let skillDir;
|
|
402
|
+
|
|
403
|
+
if (options.dir) {
|
|
404
|
+
skillDir = path.resolve(options.dir);
|
|
405
|
+
} else if (slug) {
|
|
406
|
+
skillDir = path.join(OP_SKILLS_DIR, `persona-${slug}`);
|
|
407
|
+
} else {
|
|
408
|
+
// Try current directory
|
|
409
|
+
skillDir = process.cwd();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!require('fs-extra').existsSync(path.join(skillDir, 'acn-config.json'))) {
|
|
413
|
+
printError(`No acn-config.json found in ${skillDir}. Provide a slug or --dir pointing to a generated persona pack.`);
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
const result = await registerWithAcn(skillDir, {
|
|
419
|
+
endpoint: options.endpoint,
|
|
420
|
+
dryRun: options.dryRun,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
if (options.dryRun) return;
|
|
424
|
+
|
|
425
|
+
printSuccess(`Registered with ACN!`);
|
|
426
|
+
printInfo(` Agent ID: ${result.agent_id}`);
|
|
427
|
+
printInfo(` Status: ${result.status}`);
|
|
428
|
+
printInfo(` Claim URL: ${result.claim_url}`);
|
|
429
|
+
printInfo(` Card URL: ${result.agent_card_url}`);
|
|
430
|
+
printInfo(` Heartbeat: ${result.heartbeat_endpoint}`);
|
|
431
|
+
printInfo(` Saved: acn-registration.json`);
|
|
432
|
+
} catch (e) {
|
|
433
|
+
printError(e.message);
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
303
438
|
program
|
|
304
439
|
.command('export <slug>')
|
|
305
440
|
.description('Export persona pack (with soul state) as a zip archive')
|