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.
Files changed (36) hide show
  1. package/README.md +159 -35
  2. package/bin/cli.js +137 -2
  3. package/layers/faculties/economy/SKILL.md +203 -0
  4. package/layers/faculties/economy/faculty.json +29 -0
  5. package/layers/faculties/economy/scripts/economy-guard.js +54 -0
  6. package/layers/faculties/economy/scripts/economy-hook.js +185 -0
  7. package/layers/faculties/economy/scripts/economy-lib.js +472 -0
  8. package/layers/faculties/economy/scripts/economy.js +517 -0
  9. package/layers/soul/soul-state.template.json +1 -1
  10. package/lib/economy-schema.js +62 -0
  11. package/lib/evolution.js +139 -4
  12. package/lib/generator.js +216 -4
  13. package/lib/registrar.js +155 -0
  14. package/lib/utils.js +0 -2
  15. package/package.json +1 -1
  16. package/presets/ai-girlfriend/manifest.json +1 -1
  17. package/presets/ai-girlfriend/persona.json +6 -0
  18. package/presets/base/manifest.json +1 -1
  19. package/presets/base/persona.json +6 -0
  20. package/presets/health-butler/manifest.json +1 -1
  21. package/presets/health-butler/persona.json +6 -0
  22. package/presets/life-assistant/manifest.json +1 -1
  23. package/presets/life-assistant/persona.json +6 -0
  24. package/presets/samantha/manifest.json +1 -1
  25. package/presets/samantha/persona.json +6 -0
  26. package/presets/stoic-mentor/manifest.json +1 -1
  27. package/presets/stoic-mentor/persona.json +6 -0
  28. package/schemas/acn/acn-register.schema.json +43 -0
  29. package/schemas/acn/agent-card.schema.json +99 -0
  30. package/schemas/body/embodiment.schema.json +1 -1
  31. package/schemas/evolution/influence-request.schema.json +79 -0
  32. package/schemas/signal.schema.json +1 -1
  33. package/schemas/soul/persona.schema.json +104 -0
  34. package/skills/open-persona/SKILL.md +68 -9
  35. package/templates/skill.template.md +56 -1
  36. 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** β€” on top of [OpenClaw](https://github.com/openclaw/openclaw). Inspired by [Clawra](https://github.com/SumeLabs/clawra).
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
- β”‚ └── state.json ← Evolution state (when enabled)
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
- β”œβ”€β”€ manifest.json ← Four-layer manifest (heartbeat, allowedTools, layers, meta)
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 (122 passing)
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.10.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')