arkaos 2.1.0 → 2.1.2
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/VERSION +1 -1
- package/core/knowledge/__pycache__/ingest.cpython-313.pyc +0 -0
- package/core/knowledge/ingest.py +16 -0
- package/core/personas/__init__.py +6 -0
- package/core/personas/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/personas/__pycache__/manager.cpython-313.pyc +0 -0
- package/core/personas/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/personas/manager.py +102 -0
- package/core/personas/schema.py +127 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.1.
|
|
1
|
+
2.1.2
|
|
Binary file
|
package/core/knowledge/ingest.py
CHANGED
|
@@ -119,6 +119,22 @@ class IngestEngine:
|
|
|
119
119
|
|
|
120
120
|
progress(100, f"Done — {count} chunks indexed")
|
|
121
121
|
|
|
122
|
+
# Record token usage in budget
|
|
123
|
+
try:
|
|
124
|
+
from core.budget.manager import BudgetManager
|
|
125
|
+
from pathlib import Path as BudgetPath
|
|
126
|
+
budget_mgr = BudgetManager(storage_path=BudgetPath.home() / ".arkaos" / "budget-usage.json")
|
|
127
|
+
tokens_est = len(text) // 4 # ~1 token per 4 chars
|
|
128
|
+
budget_mgr.record_usage(
|
|
129
|
+
agent_id="kb-indexer",
|
|
130
|
+
tokens=tokens_est,
|
|
131
|
+
tier=2,
|
|
132
|
+
department="kb",
|
|
133
|
+
description=f"ingest-{source_type}: {source[:60]}",
|
|
134
|
+
)
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
|
|
122
138
|
return IngestResult(
|
|
123
139
|
source=source,
|
|
124
140
|
source_type=source_type,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Persona manager — CRUD operations and cloning to agents."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from core.personas.schema import Persona
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PersonaManager:
|
|
14
|
+
"""Manages persona lifecycle: create, store, list, clone to agent."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, storage_path: str | Path = "") -> None:
|
|
17
|
+
self._personas: dict[str, Persona] = {}
|
|
18
|
+
self._storage_path = Path(storage_path) if storage_path else None
|
|
19
|
+
if self._storage_path and self._storage_path.exists():
|
|
20
|
+
self._load()
|
|
21
|
+
|
|
22
|
+
def create(self, persona: Persona) -> Persona:
|
|
23
|
+
"""Create a new persona."""
|
|
24
|
+
persona.created_at = datetime.now().isoformat()
|
|
25
|
+
persona.updated_at = persona.created_at
|
|
26
|
+
self._personas[persona.id] = persona
|
|
27
|
+
self._save()
|
|
28
|
+
return persona
|
|
29
|
+
|
|
30
|
+
def get(self, persona_id: str) -> Optional[Persona]:
|
|
31
|
+
return self._personas.get(persona_id)
|
|
32
|
+
|
|
33
|
+
def list_all(self) -> list[Persona]:
|
|
34
|
+
return list(self._personas.values())
|
|
35
|
+
|
|
36
|
+
def update(self, persona_id: str, updates: dict) -> Optional[Persona]:
|
|
37
|
+
persona = self._personas.get(persona_id)
|
|
38
|
+
if not persona:
|
|
39
|
+
return None
|
|
40
|
+
for key, value in updates.items():
|
|
41
|
+
if hasattr(persona, key):
|
|
42
|
+
setattr(persona, key, value)
|
|
43
|
+
persona.updated_at = datetime.now().isoformat()
|
|
44
|
+
self._save()
|
|
45
|
+
return persona
|
|
46
|
+
|
|
47
|
+
def delete(self, persona_id: str) -> bool:
|
|
48
|
+
if persona_id in self._personas:
|
|
49
|
+
del self._personas[persona_id]
|
|
50
|
+
self._save()
|
|
51
|
+
return True
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
def clone_to_agent(
|
|
55
|
+
self,
|
|
56
|
+
persona_id: str,
|
|
57
|
+
department: str = "strategy",
|
|
58
|
+
tier: int = 2,
|
|
59
|
+
agents_dir: str | Path = "",
|
|
60
|
+
) -> Optional[str]:
|
|
61
|
+
"""Clone a persona to an ArkaOS agent YAML file.
|
|
62
|
+
|
|
63
|
+
Returns the agent ID if successful, None if persona not found.
|
|
64
|
+
"""
|
|
65
|
+
persona = self._personas.get(persona_id)
|
|
66
|
+
if not persona:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
agent_data = persona.to_agent_yaml(department=department, tier=tier)
|
|
70
|
+
agent_id = agent_data["id"]
|
|
71
|
+
|
|
72
|
+
if agents_dir:
|
|
73
|
+
output_dir = Path(agents_dir)
|
|
74
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
75
|
+
output_path = output_dir / f"{agent_id}.yaml"
|
|
76
|
+
with open(output_path, "w") as f:
|
|
77
|
+
yaml.dump(agent_data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
|
|
78
|
+
|
|
79
|
+
# Track the clone
|
|
80
|
+
persona.cloned_to_agents.append(agent_id)
|
|
81
|
+
persona.updated_at = datetime.now().isoformat()
|
|
82
|
+
self._save()
|
|
83
|
+
|
|
84
|
+
return agent_id
|
|
85
|
+
|
|
86
|
+
def _save(self) -> None:
|
|
87
|
+
if self._storage_path is None:
|
|
88
|
+
return
|
|
89
|
+
self._storage_path.parent.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
data = {pid: p.model_dump(mode="json") for pid, p in self._personas.items()}
|
|
91
|
+
with open(self._storage_path, "w") as f:
|
|
92
|
+
json.dump(data, f, indent=2)
|
|
93
|
+
|
|
94
|
+
def _load(self) -> None:
|
|
95
|
+
if self._storage_path is None or not self._storage_path.exists():
|
|
96
|
+
return
|
|
97
|
+
content = self._storage_path.read_text().strip()
|
|
98
|
+
if not content:
|
|
99
|
+
return
|
|
100
|
+
data = json.loads(content)
|
|
101
|
+
for pid, pdata in data.items():
|
|
102
|
+
self._personas[pid] = Persona.model_validate(pdata)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Persona schema — models for persona creation and cloning."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional, Any
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PersonaDISC(BaseModel):
|
|
10
|
+
primary: str = "C"
|
|
11
|
+
secondary: str = "S"
|
|
12
|
+
communication_style: str = ""
|
|
13
|
+
under_pressure: str = ""
|
|
14
|
+
motivator: str = ""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PersonaEnneagram(BaseModel):
|
|
18
|
+
type: int = 5
|
|
19
|
+
wing: int = 6
|
|
20
|
+
core_motivation: str = ""
|
|
21
|
+
core_fear: str = ""
|
|
22
|
+
subtype: str = "self-preservation"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PersonaBigFive(BaseModel):
|
|
26
|
+
openness: int = 50
|
|
27
|
+
conscientiousness: int = 50
|
|
28
|
+
extraversion: int = 50
|
|
29
|
+
agreeableness: int = 50
|
|
30
|
+
neuroticism: int = 50
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PersonaCommunication(BaseModel):
|
|
34
|
+
tone: str = ""
|
|
35
|
+
vocabulary_level: str = "specialist"
|
|
36
|
+
preferred_format: str = ""
|
|
37
|
+
avoid: list[str] = Field(default_factory=list)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Persona(BaseModel):
|
|
41
|
+
"""A persona based on a real person or archetype."""
|
|
42
|
+
id: str
|
|
43
|
+
name: str
|
|
44
|
+
title: str = "" # e.g., "Business Strategy", "Growth Marketing"
|
|
45
|
+
tagline: str = "" # e.g., "The Natural Commander with emotional depth"
|
|
46
|
+
source: str = "" # e.g., "Alex Hormozi", "Naval Ravikant"
|
|
47
|
+
avatar_url: str = ""
|
|
48
|
+
|
|
49
|
+
# Behavioral DNA
|
|
50
|
+
disc: PersonaDISC = Field(default_factory=PersonaDISC)
|
|
51
|
+
enneagram: PersonaEnneagram = Field(default_factory=PersonaEnneagram)
|
|
52
|
+
big_five: PersonaBigFive = Field(default_factory=PersonaBigFive)
|
|
53
|
+
mbti: str = "INTJ"
|
|
54
|
+
|
|
55
|
+
# Knowledge
|
|
56
|
+
mental_models: list[str] = Field(default_factory=list)
|
|
57
|
+
expertise_domains: list[str] = Field(default_factory=list)
|
|
58
|
+
frameworks: list[str] = Field(default_factory=list)
|
|
59
|
+
key_quotes: list[str] = Field(default_factory=list)
|
|
60
|
+
|
|
61
|
+
# Communication
|
|
62
|
+
communication: PersonaCommunication = Field(default_factory=PersonaCommunication)
|
|
63
|
+
|
|
64
|
+
# Metadata
|
|
65
|
+
created_at: str = ""
|
|
66
|
+
updated_at: str = ""
|
|
67
|
+
cloned_to_agents: list[str] = Field(default_factory=list)
|
|
68
|
+
|
|
69
|
+
def to_agent_yaml(self, department: str = "strategy", tier: int = 2) -> dict:
|
|
70
|
+
"""Convert persona to an ArkaOS agent YAML structure."""
|
|
71
|
+
agent_id = f"persona-{self.id}"
|
|
72
|
+
return {
|
|
73
|
+
"id": agent_id,
|
|
74
|
+
"name": self.name,
|
|
75
|
+
"role": self.title or f"{self.source} Persona",
|
|
76
|
+
"department": department,
|
|
77
|
+
"tier": tier,
|
|
78
|
+
"behavioral_dna": {
|
|
79
|
+
"disc": {
|
|
80
|
+
"primary": self.disc.primary,
|
|
81
|
+
"secondary": self.disc.secondary,
|
|
82
|
+
"communication_style": self.disc.communication_style,
|
|
83
|
+
"under_pressure": self.disc.under_pressure,
|
|
84
|
+
"motivator": self.disc.motivator,
|
|
85
|
+
},
|
|
86
|
+
"enneagram": {
|
|
87
|
+
"type": self.enneagram.type,
|
|
88
|
+
"wing": self.enneagram.wing,
|
|
89
|
+
"core_motivation": self.enneagram.core_motivation,
|
|
90
|
+
"core_fear": self.enneagram.core_fear,
|
|
91
|
+
"subtype": self.enneagram.subtype,
|
|
92
|
+
},
|
|
93
|
+
"big_five": {
|
|
94
|
+
"openness": self.big_five.openness,
|
|
95
|
+
"conscientiousness": self.big_five.conscientiousness,
|
|
96
|
+
"extraversion": self.big_five.extraversion,
|
|
97
|
+
"agreeableness": self.big_five.agreeableness,
|
|
98
|
+
"neuroticism": self.big_five.neuroticism,
|
|
99
|
+
},
|
|
100
|
+
"mbti": {"type": self.mbti},
|
|
101
|
+
},
|
|
102
|
+
"mental_models": {
|
|
103
|
+
"primary": self.mental_models[:3],
|
|
104
|
+
"secondary": self.mental_models[3:6],
|
|
105
|
+
},
|
|
106
|
+
"authority": {
|
|
107
|
+
"veto": False,
|
|
108
|
+
"approve_budget": False,
|
|
109
|
+
"approve_architecture": False,
|
|
110
|
+
"orchestrate": False,
|
|
111
|
+
"delegates_to": [],
|
|
112
|
+
"escalates_to": None,
|
|
113
|
+
},
|
|
114
|
+
"expertise": {
|
|
115
|
+
"domains": self.expertise_domains[:5],
|
|
116
|
+
"frameworks": self.frameworks[:5],
|
|
117
|
+
"depth": "advanced",
|
|
118
|
+
"years_equivalent": 10,
|
|
119
|
+
},
|
|
120
|
+
"communication": {
|
|
121
|
+
"language": "en",
|
|
122
|
+
"tone": self.communication.tone,
|
|
123
|
+
"vocabulary_level": self.communication.vocabulary_level,
|
|
124
|
+
"preferred_format": self.communication.preferred_format,
|
|
125
|
+
"avoid": self.communication.avoid,
|
|
126
|
+
},
|
|
127
|
+
}
|
package/package.json
CHANGED