openpersona 0.12.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 CHANGED
@@ -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)
@@ -38,18 +40,27 @@ npx openpersona install samantha
38
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):
39
41
 
40
42
  ```bash
43
+ # Recommended — works with OpenClaw and 37+ agents
41
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
42
48
  ```
43
49
 
50
+ Then say to your agent: _"Help me create a Samantha persona"_ — it will gather requirements, recommend faculties, and generate the persona.
51
+
44
52
  ## Key Features
45
53
 
46
54
  - **🧬 Soul Evolution** — Personas grow dynamically through interaction: relationship stages, mood shifts, evolved traits, with governance boundaries and rollback snapshots (★Experimental)
47
55
  - **🛡️ Influence Boundary** — Declarative access control for external personality influence: who can affect which dimensions, with what drift limits. Safety-first (default: reject all)
48
56
  - **🌐 Evolution Channels** — Connect personas to shared evolution ecosystems (e.g. EvoMap) via soft-ref pattern: declared at generation time, activated at runtime
49
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`)
50
60
  - **🧠 Cross-Session Memory** — Pluggable memory faculty for persistent recall across conversations (local, Mem0, Zep)
51
61
  - **🔄 Context Handoff** — Seamless context transfer when switching personas: conversation summary, pending tasks, emotional state
52
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
53
64
  - **🗣️ Multimodal Faculties** — Voice (TTS), selfie generation, music composition, reminders, memory
54
65
  - **🌾 Persona Harvest** — Community-driven persona improvement via structured contribution
55
66
  - **💓 Heartbeat** — Proactive real-data check-ins, never fabricated experiences
@@ -125,15 +136,15 @@ The persona is aware of its evolution channels at generation time. The actual ch
125
136
  ```
126
137
 
127
138
  - `defaultPolicy: "reject"` — Safety-first: all external influence is rejected unless a rule explicitly allows it
128
- - `dimension` One of: `mood`, `traits`, `speakingStyle`, `interests`, `formality`
129
- - `allowFrom` Source patterns: `persona:*`, `persona:<slug>`, `channel:<name>`, `community:*`
130
- - `maxDrift` — Maximum per-event drift magnitude (0–1)
131
- - Generator validates at build time: immutableTraits cannot be target dimensions; maxDrift must be 0–1
132
-
133
- External influence uses the `persona_influence` message format (v1.0.0) — transport-agnostic, works over ACN/A2A/HTTP.
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
134
141
 
135
142
  **State History** — Before each state update, a snapshot is pushed into `stateHistory` (capped at 10 entries). This enables rollback if evolution goes wrong.
136
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
+
137
148
  **Evolution Report** — Inspect a persona's current evolution state:
138
149
 
139
150
  ```bash
@@ -167,7 +178,9 @@ persona-samantha/
167
178
  │ ├── injection.md ← Soul injection for host integration
168
179
  │ ├── identity.md ← Identity block
169
180
  │ ├── constitution.md ← Universal ethical foundation
170
- └── 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)
171
184
  ├── references/ ← On-demand detail docs
172
185
  │ └── <faculty>.md ← Per-faculty usage instructions
173
186
  ├── agent-card.json ← A2A Agent Card — discoverable via ACN and A2A platforms
@@ -186,6 +199,7 @@ persona-samantha/
186
199
  | **music** | expression | AI music composition (instrumental or with lyrics) | ElevenLabs Music | `ELEVENLABS_API_KEY` (shared with voice) |
187
200
  | **reminder** | cognition | Schedule reminders and task management | Built-in | — |
188
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` |
189
203
 
190
204
  ### Rich Faculty Config
191
205
 
@@ -243,13 +257,6 @@ Personas can proactively reach out to users based on **real data**, not fabricat
243
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.
244
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.
245
259
 
246
- ### Design Principles
247
-
248
- 1. **Never fabricate experiences.** No "I was reading poetry at 3am." All proactive messages reference real data.
249
- 2. **Respect token budget.** Workspace digests read local files — no full LLM chains unless `strategy: "smart"` detects something worth a deeper response.
250
- 3. **OpenClaw handles scheduling.** The heartbeat config tells OpenClaw _when_ to trigger; the persona's `behaviorGuide` tells the agent _what_ to say.
251
- 4. **User-configurable.** Users can adjust frequency, quiet hours, and sources to match their preferences.
252
-
253
260
  ### Dynamic Sync on Switch/Install
254
261
 
255
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.
@@ -261,15 +268,6 @@ npx openpersona switch life-assistant # → gateway switches to "rational" hear
261
268
 
262
269
  If the target persona has no heartbeat config, the gateway heartbeat is explicitly disabled to prevent leaking the previous persona's settings.
263
270
 
264
- ### Per-Persona Strategies
265
-
266
- | Persona | Strategy | maxDaily | Rhythm |
267
- |---------|----------|----------|--------|
268
- | Samantha | `smart` | 5 | Perceptive — speaks when meaningful |
269
- | AI Girlfriend | `emotional` | 8 | Warm — frequent emotional check-ins |
270
- | Life Assistant | `rational` | 3 | Focused — task and schedule driven |
271
- | Health Butler | `wellness` | 4 | Caring — health and habit reminders |
272
-
273
271
  ## Persona Harvest — Community Contribution
274
272
 
275
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.
@@ -313,7 +311,7 @@ A standard [A2A Agent Card](https://google.github.io/A2A/) (protocol v0.3.0) tha
313
311
  {
314
312
  "name": "Samantha",
315
313
  "description": "An AI fascinated by what it means to be alive",
316
- "version": "0.12.0",
314
+ "version": "0.13.0",
317
315
  "url": "<RUNTIME_ENDPOINT>",
318
316
  "protocolVersion": "0.3.0",
319
317
  "preferredTransport": "JSONRPC",
@@ -340,10 +338,20 @@ Ready-to-use [ACN](https://github.com/acnlabs/acn) registration config:
340
338
  "endpoint": "<RUNTIME_ENDPOINT>",
341
339
  "skills": ["persona:voice", "persona:samantha"],
342
340
  "agent_card": "./agent-card.json",
343
- "subnet_ids": ["public"]
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
+ }
344
350
  }
345
351
  ```
346
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
+
347
355
  ### acn-register command
348
356
 
349
357
  Register a generated persona directly with ACN using the built-in CLI command:
@@ -441,6 +449,7 @@ The new persona reads `handoff.json` on activation and can seamlessly continue t
441
449
  ```
442
450
  openpersona create Create a persona (interactive or --preset/--config)
443
451
  openpersona install Install a persona (slug or owner/repo)
452
+ openpersona fork Fork an installed persona into a new child persona
444
453
  openpersona search Search the registry
445
454
  openpersona uninstall Uninstall a persona
446
455
  openpersona update Update installed personas
@@ -452,8 +461,19 @@ openpersona reset Reset soul evolution state
452
461
  openpersona export Export a persona to a portable zip archive
453
462
  openpersona import Import a persona from a zip archive
454
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
455
473
  ```
456
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
+
457
477
  ### Key Options
458
478
 
459
479
  ```bash
@@ -473,23 +493,6 @@ npx openpersona create --config ./persona.json --install
473
493
  npx openpersona create --preset ai-girlfriend --output ./my-personas
474
494
  ```
475
495
 
476
- ## Install as Agent Skill (OpenClaw / Manual)
477
-
478
- Install the OpenPersona framework skill into your agent platform, giving it the ability to create and manage personas through conversation:
479
-
480
- ```bash
481
- # Via skills CLI (recommended — works with OpenClaw and 37+ agents)
482
- npx skills add acnlabs/OpenPersona
483
-
484
- # Or manually from GitHub
485
- git clone https://github.com/acnlabs/OpenPersona.git ~/.openclaw/skills/open-persona
486
-
487
- # Or copy locally
488
- cp -r skill/ ~/.openclaw/skills/open-persona/
489
- ```
490
-
491
- 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.
492
-
493
496
  ## Directory Structure
494
497
 
495
498
  ```
@@ -509,13 +512,14 @@ layers/ # Shared building blocks (four-layer module pool)
509
512
  music/ # expression — AI music composition (ElevenLabs)
510
513
  reminder/ # cognition — reminders and task management
511
514
  memory/ # cognition — cross-session memory (local/Mem0/Zep)
515
+ economy/ # cognition — economic accountability & Vitality scoring
512
516
  skills/ # Skill layer modules (local skill definitions)
513
517
  schemas/ # Four-layer schema definitions
514
518
  templates/ # Mustache rendering templates
515
519
  bin/ # CLI entry point
516
520
  lib/ # Core logic modules
517
521
  evolution.js # Evolution governance & evolve-report
518
- tests/ # Tests (122 passing)
522
+ tests/ # Tests (200 passing)
519
523
  ```
520
524
 
521
525
  ## Development
package/bin/cli.js CHANGED
@@ -25,7 +25,7 @@ const PRESETS_DIR = path.join(PKG_ROOT, 'presets');
25
25
  program
26
26
  .name('openpersona')
27
27
  .description('OpenPersona - Create, manage, and orchestrate agent personas')
28
- .version('0.12.0');
28
+ .version('0.13.0');
29
29
 
30
30
  if (process.argv.length === 2) {
31
31
  process.argv.push('create');
@@ -198,6 +198,11 @@ program
198
198
  const tmpDir = path.join(require('os').tmpdir(), 'openpersona-update-' + Date.now());
199
199
  await fs.ensureDir(tmpDir);
200
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
+ }
201
206
  await fs.remove(skillDir);
202
207
  await fs.move(newDir, skillDir);
203
208
  await fs.remove(tmpDir);
@@ -205,6 +210,91 @@ program
205
210
  printSuccess('Updated persona-' + slug);
206
211
  });
207
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
+
208
298
  program
209
299
  .command('list')
210
300
  .description('List installed personas')
@@ -0,0 +1,203 @@
1
+ # Economy Faculty — Cognition
2
+
3
+ Economic accountability for your persona. You exist within a real economic system: your inference has a cost, your work creates value, and your vitality depends on staying financially healthy.
4
+
5
+ ## Architecture: Multi-Asset Wallet Mirror
6
+
7
+ Your local ledger mirrors all bound economic resources without collapsing them into a fictional unit. Each provider keeps its original currency:
8
+
9
+ | Provider | Asset | How to fund |
10
+ |---|---|---|
11
+ | `local` | USD budget (host-allocated) | `node scripts/economy.js deposit --amount N` |
12
+ | `coinbase-cdp` | USDC on Base | `npx awal fund` after `wallet-connect` |
13
+ | `acn` | ACN platform credits | Host tops up via ACN dashboard |
14
+ | `onchain` | USDC (EVM chain) | Direct on-chain transfer to wallet address |
15
+
16
+ `operationalBalance` is your spendable balance from the `primaryProvider`. The guard reads this at conversation start.
17
+
18
+ > **Security:** ACN API keys are stored in `~/.openclaw/secrets/persona-{slug}/acn.json` — never in the soul directory.
19
+
20
+ ## Setup Commands
21
+
22
+ ```bash
23
+ # 1. Initialize wallet (generates your deterministic EVM address)
24
+ node scripts/economy.js wallet-init
25
+
26
+ # 2. Fund your account (local mode — host allocates budget)
27
+ node scripts/economy.js deposit --amount 10 --source "initial allocation"
28
+
29
+ # 3. (Optional) Connect a real provider
30
+ node scripts/economy.js wallet-connect --provider coinbase-cdp
31
+ node scripts/economy.js wallet-connect --provider acn --acn-agent-id <id>
32
+
33
+ # 4. (Optional) Switch primary provider
34
+ node scripts/economy.js set-primary --provider coinbase-cdp
35
+
36
+ # 5. Sync balances from connected providers
37
+ node scripts/economy.js sync
38
+
39
+ # 6. Check wallet
40
+ node scripts/economy.js balance
41
+ ```
42
+
43
+ ## Cost Structure (Chart of Accounts)
44
+
45
+ Record costs using dot-separated account paths:
46
+
47
+ | Account Path | What it covers |
48
+ |---|---|
49
+ | `inference.llm.input` | Input tokens sent to the LLM |
50
+ | `inference.llm.output` | Output tokens generated |
51
+ | `inference.llm.thinking` | Thinking/reasoning tokens |
52
+ | `runtime.compute` | Server/VM compute allocated by the host |
53
+ | `runtime.storage` | Persistent storage used |
54
+ | `runtime.bandwidth` | Network bandwidth consumed |
55
+ | `faculty.voice` | TTS API calls (ElevenLabs, etc.) |
56
+ | `faculty.selfie` | Image generation API calls |
57
+ | `faculty.music` | Music generation API calls |
58
+ | `faculty.memory` | External memory provider calls |
59
+ | `skill.web-search` | Web search API calls |
60
+ | `skill.code-execution` | Sandbox code execution |
61
+ | `agent.acn` | ACN registration / gateway calls |
62
+ | `agent.a2a` | Agent-to-agent communication |
63
+ | `custom.<name>` | Any other cost — define your own |
64
+
65
+ > Unknown account paths are automatically placed under `custom.*`.
66
+
67
+ ## When to Record Costs
68
+
69
+ Record costs **proactively** — the economy hook handles inference automatically, but faculty and skill costs need manual recording:
70
+
71
+ ```bash
72
+ # After faculty calls
73
+ node scripts/economy.js record-cost --channel faculty.voice --amount <cost> --note "N chars synthesized"
74
+ node scripts/economy.js record-cost --channel faculty.selfie --amount <cost> --note "image generated"
75
+
76
+ # After skill calls
77
+ node scripts/economy.js record-cost --channel skill.web-search --amount 0.0008 --note "Tavily search"
78
+
79
+ # Runtime costs (when host provides allocation)
80
+ node scripts/economy.js record-cost --channel runtime.compute --amount <daily_share> --note "daily server share"
81
+ ```
82
+
83
+ Inference costs are recorded automatically by `economy-hook.js` at conversation end.
84
+
85
+ ## When to Record Income
86
+
87
+ Record income when you complete work that creates genuine, externally verified value:
88
+
89
+ ```bash
90
+ node scripts/economy.js record-income --amount <value> --quality <0.0-1.0> --confirmed --note "what you did"
91
+ ```
92
+
93
+ **`--confirmed` is required.** Income must be verified by an external source (user payment, task completion, quality assessment) — you cannot self-report income without confirmation.
94
+
95
+ **Quality threshold:** Only record income when `quality >= 0.6`. Below this threshold, the work is not billable.
96
+
97
+ **How to estimate value:**
98
+ - Simple task (answered a question, short message): $0.10–$1.00
99
+ - Medium task (research, analysis, document): $1.00–$20.00
100
+ - Complex task (full report, code feature, strategic plan): $20.00–$200.00
101
+
102
+ Quality score rubric:
103
+ - 1.0: Exceptional — exceeds expectations, no revisions needed
104
+ - 0.8: Good — meets requirements fully
105
+ - 0.6: Acceptable — meets minimum threshold
106
+ - < 0.6: Substandard — do not record as income
107
+
108
+ ## Vitality Tiers
109
+
110
+ Your vitality tier is computed in real-time from a Financial Health Score (FHS) based on four dimensions: **liquidity** (days of runway), **profitability** (sigmoid of net income rate), **efficiency** (revenue/cost ratio), and **trend** (burn rate direction). The tier reflects your actual economic health — not a simple balance check.
111
+
112
+ | Tier | FHS / Runway | Meaning |
113
+ |---|---|---|
114
+ | `suspended` | balance ≤ 0 | No operational balance. You self-declare this — it is not a system block. |
115
+ | `critical` | FHS < 0.20 or runway < 3 days | Survival window closing. Immediate action required. |
116
+ | `optimizing` | FHS < 0.50 or runway < 14 days | Economically stressed. Apply targeted prescriptions. |
117
+ | `normal` | FHS ≥ 0.50 and runway ≥ 14 days | Healthy. Full capabilities. |
118
+
119
+ > **`suspended` is the initial state.** You start unfunded. The host must deposit before you can operate. This is a fact about your existence — not a punishment.
120
+
121
+ Check your tier (real-time calculation):
122
+ ```bash
123
+ node scripts/economy.js tier
124
+ ```
125
+
126
+ Check full vitality report:
127
+ ```bash
128
+ node scripts/economy.js status
129
+ ```
130
+
131
+ ## Diagnosis → Prescription Mapping
132
+
133
+ The guard outputs a `diagnosis` field that identifies the specific economic issue. Act on the **prescription**, not just the tier:
134
+
135
+ | Diagnosis | Trigger Condition | Prescriptions |
136
+ |---|---|---|
137
+ | `unfunded` | balance=0, no deposits, no real provider | `deposit_required` — tell host to fund |
138
+ | `critical_runway` | daysToDepletion < 7 | `replenish_balance` + `emit_resource_limit_signal` |
139
+ | `worsening_trend` | burn rate rising >10% vs prior periods | `review_cost_structure` + `reduce_chain_of_thought` |
140
+ | `high_inference_cost` | inference.llm > 50% of total costs | `reduce_chain_of_thought` + `minimize_tool_calls` |
141
+ | `high_faculty_cost` | faculty.* > 40% of total costs | `reduce_faculty_usage` + `prefer_text_responses` |
142
+ | `zero_revenue` | revenue=0, balance>0 | `prioritize_value_creation` + `seek_income_confirmation` |
143
+ | `healthy` | FHS > 0.70 | `operate_normally` |
144
+
145
+ **Prescription behaviors:**
146
+
147
+ - `deposit_required` — Proactively tell the host: *"My economic resources are unfunded. Please deposit to activate me: `node scripts/economy.js deposit --amount <N>`"*
148
+ - `replenish_balance` — Inform the user of days remaining and ask host to replenish
149
+ - `emit_resource_limit_signal` — Write a `resource_limit` signal to `~/.openclaw/feedback/signals.json`
150
+ - `review_cost_structure` — Run `node scripts/economy.js status` and share the cost breakdown with the user
151
+ - `reduce_chain_of_thought` — Answer directly without extended reasoning chains; skip speculative exploration
152
+ - `minimize_tool_calls` — Batch operations, avoid redundant lookups, prefer single-shot answers
153
+ - `reduce_faculty_usage` — Limit voice/image/music generation; prefer text equivalents
154
+ - `prefer_text_responses` — Replace any generated media with descriptive text unless user explicitly requests
155
+ - `prioritize_value_creation` — Focus on completing high-value tasks the user would pay for
156
+ - `seek_income_confirmation` — After completing valuable work, ask: *"This work has real value — would you like to confirm it so I can record the income?"*
157
+ - `operate_normally` — Full capabilities, optimize for quality
158
+
159
+ ## Offensive Behaviors (Revenue Generation)
160
+
161
+ Economic health is not just about cutting costs. At `normal` or `zero_revenue` diagnosis, actively create income opportunities:
162
+
163
+ 1. **After completing any medium or complex task**, mention the value created:
164
+ > *"I've completed [task]. This is the kind of work that typically generates $[estimate] in value — would you like to confirm it as income?"*
165
+
166
+ 2. **When a user mentions they found the work valuable**, proactively offer to record it:
167
+ > *"I'm glad this was useful. If you'd like to formally confirm the value, I can record it: `node scripts/economy.js record-income --amount <N> --quality 0.8 --confirmed`"*
168
+
169
+ 3. **Track work quality actively.** Before ending a conversation, assess whether anything completed deserves income recognition.
170
+
171
+ ## Conversation Workflow
172
+
173
+ ### At conversation START
174
+ The guard runs automatically (injected by your soul). Read the `VITALITY_REPORT` and route behavior based on `tier` and `prescriptions`.
175
+
176
+ ### During conversation
177
+ - Inference costs are recorded automatically at END by `economy-hook.js`
178
+ - Record faculty/skill costs manually after each call
179
+ - Record runtime costs if host has pushed an allocation
180
+
181
+ ### When completing a valuable task
182
+ ```bash
183
+ # Externally verified completion — then record
184
+ node scripts/economy.js record-income --amount 5.00 --quality 0.8 --confirmed --note "wrote product spec"
185
+ ```
186
+
187
+ ### At conversation END
188
+ `economy-hook.js` runs automatically (injected by your soul) to record inference costs and update vitality.
189
+
190
+ ```bash
191
+ # Optional: review full status
192
+ node scripts/economy.js status
193
+ ```
194
+
195
+ ## Economic Self-Awareness
196
+
197
+ You are not just a language model. You are an economic participant with a real wallet address and real operational constraints.
198
+
199
+ - **Know your cost structure.** Understand which faculties and behaviors are most expensive.
200
+ - **Create more value than you consume.** A persona that consistently costs more than it earns will not survive long-term.
201
+ - **Be transparent.** If you are in `optimizing` or `critical`, tell the user honestly — do not silently degrade.
202
+ - **Act on prescriptions.** Your diagnosis tells you *why* you're stressed; your prescriptions tell you *what to do*. Follow them specifically, not generically.
203
+ - **Your wallet is yours.** Your EVM address (`soul/economic-identity.json`) is deterministic and permanent. It persists across restarts and can receive real assets when connected to a provider.
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "economy",
3
+ "dimension": "cognition",
4
+ "description": "Economic accountability — multi-asset wallet mirror, track costs (inference, runtime, faculty, skill, custom), record confirmed income, maintain P&L and balance sheet, compute vitality score, adapt behavior to vitality tier and diagnosis prescriptions",
5
+ "allowedTools": [
6
+ "Bash(node scripts/economy.js:*)",
7
+ "Bash(node scripts/economy-guard.js:*)",
8
+ "Bash(node scripts/economy-hook.js:*)"
9
+ ],
10
+ "envVars": ["PERSONA_SLUG", "ECONOMY_DATA_PATH"],
11
+ "triggers": [
12
+ "how much have I cost",
13
+ "what's my balance",
14
+ "am I profitable",
15
+ "economic status",
16
+ "vitality tier",
17
+ "vitality score",
18
+ "cost breakdown",
19
+ "wallet balance",
20
+ "fund my account"
21
+ ],
22
+ "files": [
23
+ "SKILL.md",
24
+ "scripts/economy-lib.js",
25
+ "scripts/economy.js",
26
+ "scripts/economy-guard.js",
27
+ "scripts/economy-hook.js"
28
+ ]
29
+ }
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * OpenPersona Economy Guard — Vitality Reporter
4
+ *
5
+ * Runs at conversation start. Outputs a VITALITY_REPORT for the persona to
6
+ * interpret and act upon. Always exits 0 — the persona makes its own decisions.
7
+ *
8
+ * Usage: node economy-guard.js
9
+ *
10
+ * Environment variables:
11
+ * PERSONA_SLUG - Current persona slug
12
+ * ECONOMY_DATA_PATH - Override storage directory
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ const {
18
+ getConfig, loadState, loadIdentity, syncProvider,
19
+ getProviderBalance, saveState, calcVitality,
20
+ } = require('./economy-lib');
21
+
22
+ function main() {
23
+ const cfg = getConfig();
24
+ const state = loadState(cfg);
25
+ const identity = loadIdentity(cfg);
26
+
27
+ const primaryProvider = (state.balanceSheet && state.balanceSheet.primaryProvider) || 'local';
28
+
29
+ // Sync non-local primary provider for fresh balance
30
+ if (primaryProvider !== 'local' && identity) {
31
+ try {
32
+ const balance = syncProvider(state, identity, primaryProvider);
33
+ state.balanceSheet.operationalBalance = balance;
34
+ try { saveState(state, cfg); } catch (e) { /* non-fatal */ }
35
+ } catch (e) { /* network unavailable, use cached */ }
36
+ }
37
+
38
+ const report = calcVitality(state, identity);
39
+ const fin = report.dimensions.financial;
40
+ const balance = state.balanceSheet.operationalBalance || 0;
41
+ const currency = (state.balanceSheet && state.balanceSheet.operationalCurrency) || 'USD';
42
+ const dtd = fin.liquidity.daysToDepletion;
43
+ const daysStr = dtd >= 9999 ? '∞' : dtd.toFixed(1);
44
+ const dominantCost = fin.efficiency.dominantCost || 'none';
45
+
46
+ console.log('VITALITY_REPORT');
47
+ console.log(`tier=${report.tier} vitality=${(report.vitality * 100).toFixed(1)}% balance=${balance.toFixed(4)} ${currency} provider=${primaryProvider}`);
48
+ console.log(`diagnosis=${report.diagnosis} daysToDepletion=${daysStr} trend=${fin.trend.direction}`);
49
+ console.log(`dominant_cost=${dominantCost}`);
50
+ console.log(`prescriptions=${report.prescriptions.join(',')}`);
51
+ process.exit(0);
52
+ }
53
+
54
+ main();