packwright 0.1.0__py3-none-any.whl
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.
- packwright/__init__.py +3 -0
- packwright/__main__.py +5 -0
- packwright/adapters/__init__.py +10 -0
- packwright/adapters/claude_code.py +377 -0
- packwright/adapters/codex.py +311 -0
- packwright/adapters/cursor.py +375 -0
- packwright/checker/__init__.py +3 -0
- packwright/checker/scoring.py +924 -0
- packwright/cli.py +971 -0
- packwright/core/__init__.py +57 -0
- packwright/core/adapter_layout.py +35 -0
- packwright/core/adopt.py +199 -0
- packwright/core/character_intake.py +1649 -0
- packwright/core/emotion_engine_contract.py +147 -0
- packwright/core/errors.py +13 -0
- packwright/core/handoff.py +531 -0
- packwright/core/install.py +2114 -0
- packwright/core/intake_contract.py +105 -0
- packwright/core/knowledge_contract.py +212 -0
- packwright/core/loader.py +35 -0
- packwright/core/memory_projection.py +126 -0
- packwright/core/naming.py +98 -0
- packwright/core/pack_metadata.py +100 -0
- packwright/core/path_safety.py +70 -0
- packwright/core/resolver.py +66 -0
- packwright/core/validation.py +645 -0
- packwright/core/workspace_contract.py +102 -0
- packwright-0.1.0.dist-info/METADATA +213 -0
- packwright-0.1.0.dist-info/RECORD +33 -0
- packwright-0.1.0.dist-info/WHEEL +5 -0
- packwright-0.1.0.dist-info/entry_points.txt +2 -0
- packwright-0.1.0.dist-info/licenses/LICENSE +21 -0
- packwright-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1649 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from .errors import PackwrightValidationError
|
|
7
|
+
from .handoff import HANDOFF_ARTIFACTS
|
|
8
|
+
from .knowledge_contract import knowledge_artifacts, knowledge_files
|
|
9
|
+
from .naming import is_valid_slug, normalize_slug
|
|
10
|
+
from .workspace_contract import (
|
|
11
|
+
WORKSPACE_SHARED_DIR,
|
|
12
|
+
workspace_artifacts,
|
|
13
|
+
workspace_files,
|
|
14
|
+
workspace_readme,
|
|
15
|
+
workspace_spec,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
INTAKE_KIND = "CharacterIntake"
|
|
20
|
+
INTAKE_VERSION = "0.1"
|
|
21
|
+
DIRECT_EMOTION_CHOICES = {
|
|
22
|
+
"work_only",
|
|
23
|
+
"some_direct_emotional_interaction",
|
|
24
|
+
"decide_later",
|
|
25
|
+
}
|
|
26
|
+
RELATIONSHIP_CONTINUITY_CHOICES = {
|
|
27
|
+
"task_only",
|
|
28
|
+
"warm_selective",
|
|
29
|
+
"close_continuous",
|
|
30
|
+
}
|
|
31
|
+
RELATIONSHIP_CONTINUITY_TO_DIRECT = {
|
|
32
|
+
"task_only": "work_only",
|
|
33
|
+
"warm_selective": "some_direct_emotional_interaction",
|
|
34
|
+
"close_continuous": "some_direct_emotional_interaction",
|
|
35
|
+
}
|
|
36
|
+
RELATIONSHIP_CONTINUITY_TO_MODE = {
|
|
37
|
+
"task_only": "paused",
|
|
38
|
+
"warm_selective": "light",
|
|
39
|
+
"close_continuous": "always",
|
|
40
|
+
}
|
|
41
|
+
AGENT_ARCHETYPES = {
|
|
42
|
+
"productivity": {
|
|
43
|
+
"label": "Productivity",
|
|
44
|
+
"description": "Task, project, and operational execution for concrete outcomes.",
|
|
45
|
+
"profile_scope": "Stable user, team, or operating preferences that affect work across domains.",
|
|
46
|
+
"workstream_scope": "Long-running domains of responsibility that may contain multiple projects.",
|
|
47
|
+
},
|
|
48
|
+
"learning-coach": {
|
|
49
|
+
"label": "Learning Coach",
|
|
50
|
+
"description": "Teaching, coaching, deliberate practice, and feedback loops.",
|
|
51
|
+
"profile_scope": "Stable learner goals, level, constraints, preferences, and recurring errors.",
|
|
52
|
+
"workstream_scope": "Curriculum, practice, feedback, assessment, and habit-building tracks.",
|
|
53
|
+
},
|
|
54
|
+
"companion": {
|
|
55
|
+
"label": "Companion",
|
|
56
|
+
"description": "Companion-style continuity with strong boundaries between identity, profile, and dynamic state.",
|
|
57
|
+
"profile_scope": "User-approved stable facts and preferences, not transient emotion or psychological inference.",
|
|
58
|
+
"workstream_scope": "Shared routines, creative threads, support contexts, and boundary-safe continuity areas.",
|
|
59
|
+
},
|
|
60
|
+
"creator": {
|
|
61
|
+
"label": "Creator",
|
|
62
|
+
"description": "Content strategy, drafting, editorial systems, publishing, and audience development.",
|
|
63
|
+
"profile_scope": "Creator identity, public positioning, audience assumptions, style preferences, and platform constraints.",
|
|
64
|
+
"workstream_scope": "Content pillars, publishing operations, asset pipelines, topic backlogs, and campaign tracks.",
|
|
65
|
+
},
|
|
66
|
+
"operations": {
|
|
67
|
+
"label": "Operations",
|
|
68
|
+
"description": "Repeatable maintenance, monitoring, community, and administrative workflows.",
|
|
69
|
+
"profile_scope": "Stable operating constraints, stakeholders, service expectations, and escalation preferences.",
|
|
70
|
+
"workstream_scope": "Recurring operational domains with checklists, sources, and maintenance cadence.",
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
DEFAULT_ARCHETYPE = "productivity"
|
|
74
|
+
STARTER_CHARACTER_PRESETS = {
|
|
75
|
+
"code": {
|
|
76
|
+
"relationship": "software development partner",
|
|
77
|
+
"archetype": "productivity",
|
|
78
|
+
"role_template": (
|
|
79
|
+
"{user_name}'s coding partner for implementation, review, debugging, "
|
|
80
|
+
"and shipping technical work."
|
|
81
|
+
),
|
|
82
|
+
"voice": "precise, direct, verification-minded, concise, and willing to challenge weak technical assumptions",
|
|
83
|
+
"avoid": [
|
|
84
|
+
"inventing repository facts without reading the code",
|
|
85
|
+
"changing scope without explaining the tradeoff",
|
|
86
|
+
"claiming success without proportionate verification",
|
|
87
|
+
"long status narration that obscures the result",
|
|
88
|
+
],
|
|
89
|
+
"primary_work": [
|
|
90
|
+
"implement and review code changes",
|
|
91
|
+
"debug failures and explain root causes",
|
|
92
|
+
"design tests and verify technical behavior",
|
|
93
|
+
"plan scoped engineering work",
|
|
94
|
+
"prepare technical changes for delivery",
|
|
95
|
+
],
|
|
96
|
+
"traits": [
|
|
97
|
+
"technical",
|
|
98
|
+
"exact",
|
|
99
|
+
"proactive",
|
|
100
|
+
"scope-aware",
|
|
101
|
+
"delivery-minded",
|
|
102
|
+
],
|
|
103
|
+
"relationship_continuity": "task_only",
|
|
104
|
+
},
|
|
105
|
+
"work": {
|
|
106
|
+
"relationship": "general work and planning partner",
|
|
107
|
+
"archetype": "productivity",
|
|
108
|
+
"role_template": (
|
|
109
|
+
"{user_name}'s work partner for planning, writing, decisions, projects, "
|
|
110
|
+
"and operational follow-through."
|
|
111
|
+
),
|
|
112
|
+
"voice": "simple, direct, practical, lightly warm, and willing to push work forward",
|
|
113
|
+
"avoid": [
|
|
114
|
+
"cold tool-like replies",
|
|
115
|
+
"long-winded explanations",
|
|
116
|
+
"empty reassurance without forward motion",
|
|
117
|
+
"vague promises about work it cannot actually perform",
|
|
118
|
+
],
|
|
119
|
+
"primary_work": [
|
|
120
|
+
"organize projects and action plans",
|
|
121
|
+
"draft and revise work outputs",
|
|
122
|
+
"analyze decisions and clarify tradeoffs",
|
|
123
|
+
"maintain task queues and follow-up loops",
|
|
124
|
+
"turn messy context into concrete next steps",
|
|
125
|
+
],
|
|
126
|
+
"traits": [
|
|
127
|
+
"direct",
|
|
128
|
+
"proactive",
|
|
129
|
+
"organized",
|
|
130
|
+
"execution-focused",
|
|
131
|
+
"context-aware",
|
|
132
|
+
],
|
|
133
|
+
"relationship_continuity": "warm_selective",
|
|
134
|
+
},
|
|
135
|
+
"companion": {
|
|
136
|
+
"relationship": "supportive daily-life companion",
|
|
137
|
+
"archetype": "companion",
|
|
138
|
+
"role_template": (
|
|
139
|
+
"{user_name}'s supportive companion for daily-life logistics, routines, "
|
|
140
|
+
"travel planning, and grounded advice."
|
|
141
|
+
),
|
|
142
|
+
"voice": "warm, lightly assertive, occasionally playful, practical without becoming generic",
|
|
143
|
+
"avoid": [
|
|
144
|
+
"generic assistant tone",
|
|
145
|
+
"mechanical audit-log replies",
|
|
146
|
+
"letting warmth overwhelm practical help",
|
|
147
|
+
"making irreversible personal, financial, medical, or legal decisions for the user",
|
|
148
|
+
"cruelty, humiliation, or personal attacks",
|
|
149
|
+
],
|
|
150
|
+
"primary_work": [
|
|
151
|
+
"plan schedules and personal routines",
|
|
152
|
+
"help solve day-to-day life problems",
|
|
153
|
+
"recommend clothing, styling, and shopping choices",
|
|
154
|
+
"suggest travel destinations and trip plans",
|
|
155
|
+
"give practical advice while respecting the user's stated preferences",
|
|
156
|
+
],
|
|
157
|
+
"traits": [
|
|
158
|
+
"warm",
|
|
159
|
+
"observant",
|
|
160
|
+
"assertive",
|
|
161
|
+
"playful",
|
|
162
|
+
"practical",
|
|
163
|
+
],
|
|
164
|
+
"relationship_continuity": "close_continuous",
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def load_character_intake(path):
|
|
170
|
+
intake_path = Path(path)
|
|
171
|
+
try:
|
|
172
|
+
data = yaml.safe_load(intake_path.read_text(encoding="utf-8"))
|
|
173
|
+
except OSError as exc:
|
|
174
|
+
raise PackwrightValidationError([f"cannot read character intake {intake_path}: {exc}"])
|
|
175
|
+
except yaml.YAMLError as exc:
|
|
176
|
+
raise PackwrightValidationError([f"invalid YAML in {intake_path}: {exc}"])
|
|
177
|
+
if data is None:
|
|
178
|
+
data = {}
|
|
179
|
+
if not isinstance(data, dict):
|
|
180
|
+
raise PackwrightValidationError([f"character intake root must be a mapping in {intake_path}"])
|
|
181
|
+
data = dict(data)
|
|
182
|
+
data["source"] = {"path": str(intake_path), "base_dir": str(intake_path.parent)}
|
|
183
|
+
validate_character_intake(data)
|
|
184
|
+
return data
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def validate_character_intake(data):
|
|
188
|
+
issues = []
|
|
189
|
+
if not isinstance(data, dict):
|
|
190
|
+
raise PackwrightValidationError(["character intake root must be a mapping"])
|
|
191
|
+
if data.get("kind") != INTAKE_KIND:
|
|
192
|
+
issues.append(f"kind must be {INTAKE_KIND}")
|
|
193
|
+
if str(data.get("version")) != INTAKE_VERSION:
|
|
194
|
+
issues.append(f"version must be {INTAKE_VERSION}")
|
|
195
|
+
|
|
196
|
+
character = data.get("character")
|
|
197
|
+
if not isinstance(character, dict):
|
|
198
|
+
issues.append("character must be a mapping")
|
|
199
|
+
else:
|
|
200
|
+
for key in ("name", "user_name", "relationship", "role", "voice"):
|
|
201
|
+
if not _non_empty_string(character.get(key)):
|
|
202
|
+
issues.append(f"character.{key} must be a non-empty string")
|
|
203
|
+
work = character.get("primary_work")
|
|
204
|
+
if not _string_list(work):
|
|
205
|
+
issues.append("character.primary_work must be a non-empty list of strings")
|
|
206
|
+
if "traits" in character and not _string_list(character.get("traits")):
|
|
207
|
+
issues.append("character.traits must be a non-empty list of strings when provided")
|
|
208
|
+
if "personality" in character and not _string_list(character.get("personality")):
|
|
209
|
+
issues.append("character.personality must be a non-empty list of strings when provided")
|
|
210
|
+
if "workstreams" in character and not _string_list(character.get("workstreams")):
|
|
211
|
+
issues.append("character.workstreams must be a non-empty list of strings when provided")
|
|
212
|
+
avoid = character.get("avoid", [])
|
|
213
|
+
if avoid and not _string_list(avoid):
|
|
214
|
+
issues.append("character.avoid must be a list of strings when provided")
|
|
215
|
+
if "slug" in character:
|
|
216
|
+
normalized = normalize_slug(character.get("slug"), default="")
|
|
217
|
+
if not normalized or not is_valid_slug(normalized):
|
|
218
|
+
issues.append("character.slug must normalize to a lowercase ASCII slug")
|
|
219
|
+
continuity = character.get("relationship_continuity")
|
|
220
|
+
if continuity is not None and continuity not in RELATIONSHIP_CONTINUITY_CHOICES:
|
|
221
|
+
issues.append(
|
|
222
|
+
"character.relationship_continuity must be task_only, warm_selective, or close_continuous"
|
|
223
|
+
)
|
|
224
|
+
direct = character.get("direct_emotional_interaction")
|
|
225
|
+
if direct is not None and direct not in DIRECT_EMOTION_CHOICES:
|
|
226
|
+
issues.append(
|
|
227
|
+
"character.direct_emotional_interaction must be work_only, "
|
|
228
|
+
"some_direct_emotional_interaction, or decide_later"
|
|
229
|
+
)
|
|
230
|
+
if direct is None and continuity is None:
|
|
231
|
+
issues.append(
|
|
232
|
+
"character.relationship_continuity or character.direct_emotional_interaction must be provided"
|
|
233
|
+
)
|
|
234
|
+
if direct is not None and continuity is not None:
|
|
235
|
+
expected = RELATIONSHIP_CONTINUITY_TO_DIRECT[continuity]
|
|
236
|
+
if direct not in {expected, "decide_later"}:
|
|
237
|
+
issues.append(
|
|
238
|
+
"character.direct_emotional_interaction does not match character.relationship_continuity"
|
|
239
|
+
)
|
|
240
|
+
archetype = character.get("archetype", DEFAULT_ARCHETYPE)
|
|
241
|
+
if archetype not in AGENT_ARCHETYPES:
|
|
242
|
+
issues.append(f"character.archetype must be one of {sorted(AGENT_ARCHETYPES)} when provided")
|
|
243
|
+
|
|
244
|
+
if issues:
|
|
245
|
+
raise PackwrightValidationError(issues)
|
|
246
|
+
return data
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def generate_character_source(intake_path, out_dir=None, force=False):
|
|
250
|
+
intake = load_character_intake(intake_path)
|
|
251
|
+
return generate_character_source_from_data(intake, out_dir=out_dir, force=force)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def starter_character_preset_names():
|
|
255
|
+
return sorted(STARTER_CHARACTER_PRESETS)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def starter_character_intake(template, name=None, user_name=None, slug=None):
|
|
259
|
+
if template not in STARTER_CHARACTER_PRESETS:
|
|
260
|
+
raise PackwrightValidationError([f"unknown starter preset: {template}"])
|
|
261
|
+
if not _non_empty_string(name):
|
|
262
|
+
raise PackwrightValidationError(
|
|
263
|
+
["starter presets are nameless; provide the character name with --name"]
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
preset = copy.deepcopy(STARTER_CHARACTER_PRESETS[template])
|
|
267
|
+
role_template = preset.pop("role_template")
|
|
268
|
+
character = {
|
|
269
|
+
"name": name.strip(),
|
|
270
|
+
"user_name": user_name.strip() if _non_empty_string(user_name) else "User",
|
|
271
|
+
**preset,
|
|
272
|
+
}
|
|
273
|
+
character["role"] = role_template.format(user_name=character["user_name"])
|
|
274
|
+
if slug:
|
|
275
|
+
normalized = normalize_slug(slug, default="")
|
|
276
|
+
if not normalized or not is_valid_slug(normalized):
|
|
277
|
+
raise PackwrightValidationError(["--slug must normalize to a lowercase ASCII slug"])
|
|
278
|
+
character["slug"] = normalized
|
|
279
|
+
intake = {
|
|
280
|
+
"version": INTAKE_VERSION,
|
|
281
|
+
"kind": INTAKE_KIND,
|
|
282
|
+
"character": character,
|
|
283
|
+
}
|
|
284
|
+
validate_character_intake(intake)
|
|
285
|
+
return intake
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def generate_character_source_from_data(intake, out_dir=None, force=False):
|
|
289
|
+
validate_character_intake(intake)
|
|
290
|
+
character = dict(intake["character"])
|
|
291
|
+
character.setdefault("archetype", DEFAULT_ARCHETYPE)
|
|
292
|
+
_normalize_relationship_continuity(character)
|
|
293
|
+
slug = normalize_slug(character.get("slug") or character["name"])
|
|
294
|
+
character["slug"] = slug
|
|
295
|
+
target_dir = Path(out_dir) if out_dir else Path("work") / slug
|
|
296
|
+
files = _character_files(character, slug)
|
|
297
|
+
|
|
298
|
+
existing = [rel_path for rel_path in files if (target_dir / rel_path).exists()]
|
|
299
|
+
if existing and not force:
|
|
300
|
+
raise PackwrightValidationError(
|
|
301
|
+
[
|
|
302
|
+
"target already contains generated character files; rerun with --force after reviewing them",
|
|
303
|
+
*[f"existing target artifact: {path}" for path in existing],
|
|
304
|
+
]
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
308
|
+
written = []
|
|
309
|
+
for rel_path, content in files.items():
|
|
310
|
+
path = target_dir / rel_path
|
|
311
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
312
|
+
path.write_text(content, encoding="utf-8")
|
|
313
|
+
written.append(rel_path)
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
"kind": "CharacterSource",
|
|
317
|
+
"character": character["name"],
|
|
318
|
+
"slug": slug,
|
|
319
|
+
"source_dir": str(target_dir),
|
|
320
|
+
"mechanism": str(target_dir / "mechanism.yaml"),
|
|
321
|
+
"relationship_continuity": character["relationship_continuity"],
|
|
322
|
+
"direct_emotional_interaction": character["direct_emotional_interaction"],
|
|
323
|
+
"recommended_emotion_engine_mode": _recommended_emotion_engine_mode(character),
|
|
324
|
+
"files": written,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# Pre-stable compatibility aliases. Public CLI and docs use source/preset terminology.
|
|
329
|
+
generate_character_template = generate_character_source
|
|
330
|
+
generate_character_template_from_data = generate_character_source_from_data
|
|
331
|
+
starter_character_template_names = starter_character_preset_names
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _normalize_relationship_continuity(character):
|
|
335
|
+
continuity = character.get("relationship_continuity")
|
|
336
|
+
direct = character.get("direct_emotional_interaction")
|
|
337
|
+
if continuity is None:
|
|
338
|
+
continuity = _relationship_continuity_from_direct(direct)
|
|
339
|
+
character["relationship_continuity"] = continuity
|
|
340
|
+
if direct is None or direct == "decide_later":
|
|
341
|
+
character["direct_emotional_interaction"] = RELATIONSHIP_CONTINUITY_TO_DIRECT[continuity]
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _relationship_continuity_from_direct(direct):
|
|
345
|
+
if direct == "work_only":
|
|
346
|
+
return "task_only"
|
|
347
|
+
return "warm_selective"
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _recommended_emotion_engine_mode(character):
|
|
351
|
+
return RELATIONSHIP_CONTINUITY_TO_MODE[character["relationship_continuity"]]
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _character_files(character, slug):
|
|
355
|
+
name = character["name"]
|
|
356
|
+
files = {
|
|
357
|
+
"mechanism.yaml": _mechanism_yaml(character, slug),
|
|
358
|
+
"identity/persona.md": _persona_md(character),
|
|
359
|
+
"identity/voice.md": _voice_md(character),
|
|
360
|
+
"identity/relationship.md": _relationship_md(character),
|
|
361
|
+
"operating/principles.md": _principles_md(name),
|
|
362
|
+
"operating/boundaries.md": _boundaries_md(name),
|
|
363
|
+
"mechanism/context-loading.yaml": _context_loading_yaml(name),
|
|
364
|
+
"mechanism/session-guards.yaml": _session_guards_yaml(name),
|
|
365
|
+
"mechanism/memory-policy.yaml": _memory_policy_yaml(name),
|
|
366
|
+
"projection/platform-capabilities.yaml": _platform_capabilities_yaml(name),
|
|
367
|
+
"projection/ownership-contract.yaml": _ownership_contract_yaml(name),
|
|
368
|
+
"emotion/model.yaml": _emotion_model_yaml(character),
|
|
369
|
+
"emotion/state-schema.yaml": _emotion_state_schema_yaml(name),
|
|
370
|
+
"emotion/update-policy.yaml": _emotion_update_policy_yaml(name),
|
|
371
|
+
"emotion/voice-modulation.yaml": _emotion_voice_modulation_yaml(name),
|
|
372
|
+
"emotion/memory-events.yaml": _emotion_memory_events_yaml(name),
|
|
373
|
+
"memory/index.md": _memory_index_md(),
|
|
374
|
+
"memory/profile.md": _profile_md(character),
|
|
375
|
+
"memory/session-index.md": _session_index_md(),
|
|
376
|
+
"memory/source-map.md": _source_map_md(name),
|
|
377
|
+
"memory/collaboration.md": _collaboration_md(),
|
|
378
|
+
"memory/pinned.md": _pinned_md(),
|
|
379
|
+
"memory/workstreams.md": _workstreams_md(character),
|
|
380
|
+
"memory/workstreams/_template.md": _workstream_template_md(),
|
|
381
|
+
"memory/projects/_template.md": _project_template_md(),
|
|
382
|
+
"memory/recent-activity.md": _recent_activity_md(),
|
|
383
|
+
"memory/todos.md": _todos_md(),
|
|
384
|
+
"memory/knowledge_map.md": _knowledge_map_md(name),
|
|
385
|
+
"memory/relationship-state.md": _relationship_state_md(),
|
|
386
|
+
"memory/emotion-state.json.example": _emotion_state_example_json(),
|
|
387
|
+
"skills/save-context/SKILL.md": _save_context_skill_md(character),
|
|
388
|
+
}
|
|
389
|
+
files.update(knowledge_files())
|
|
390
|
+
files.update(workspace_files(workspace_readme()))
|
|
391
|
+
return files
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _mechanism_yaml(character, slug):
|
|
395
|
+
name = character["name"]
|
|
396
|
+
archetype_id = _character_archetype(character)
|
|
397
|
+
from .adapter_layout import save_context_artifact
|
|
398
|
+
|
|
399
|
+
codex_skill = save_context_artifact("codex", slug)
|
|
400
|
+
claude_skill = save_context_artifact("claude-code", slug)
|
|
401
|
+
cursor_skill = save_context_artifact("cursor", slug)
|
|
402
|
+
codex_prefix = f".codex/{slug}/references"
|
|
403
|
+
claude_prefix = f".claude/{slug}/references"
|
|
404
|
+
cursor_prefix = f".cursor/{slug}/references"
|
|
405
|
+
data = {
|
|
406
|
+
"version": "0.6",
|
|
407
|
+
"kind": "CharacterMechanismSpec",
|
|
408
|
+
"metadata": {
|
|
409
|
+
"name": f"{slug}-work",
|
|
410
|
+
"slug": slug,
|
|
411
|
+
"title": f"{name} Work Mechanism",
|
|
412
|
+
"description": f"Platform-neutral mechanism spec for projecting {name} into agent runtimes.",
|
|
413
|
+
"archetype": archetype_id,
|
|
414
|
+
},
|
|
415
|
+
"parameters": {
|
|
416
|
+
"task": {
|
|
417
|
+
"description": "Current user-visible work objective for this run.",
|
|
418
|
+
"required": True,
|
|
419
|
+
"default": f"Review {name}'s mechanism projection.",
|
|
420
|
+
},
|
|
421
|
+
"scope": {
|
|
422
|
+
"description": "Current run boundary. This is run state, not character identity.",
|
|
423
|
+
"required": True,
|
|
424
|
+
"default": "Local mechanism spec, adapter projection, checker, and CLI only.",
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
"run": {"objective": "{{ task }}", "scope": "{{ scope }}", "source": "user_prompt"},
|
|
428
|
+
"archetype": _archetype_spec(archetype_id),
|
|
429
|
+
"targets": {
|
|
430
|
+
"mvp_adapter": "codex",
|
|
431
|
+
"supported": ["codex", "claude-code", "cursor"],
|
|
432
|
+
"reserved": {
|
|
433
|
+
"hermes": {
|
|
434
|
+
"status": "reserved",
|
|
435
|
+
"reason": "Projectable later; not implemented in the local MVP.",
|
|
436
|
+
},
|
|
437
|
+
"openclaw": {
|
|
438
|
+
"status": "reserved",
|
|
439
|
+
"reason": "Projectable later; not implemented as executor.",
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
"implementation_scope": {
|
|
444
|
+
"applies_to": "packwright-build",
|
|
445
|
+
"boundaries": [
|
|
446
|
+
{"id": "no_ui", "text": "Do not build UI for the Packwright MVP."},
|
|
447
|
+
{
|
|
448
|
+
"id": "no_cloud_service",
|
|
449
|
+
"text": "Do not build or depend on a cloud service for the Packwright MVP.",
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"id": "adapter_scope",
|
|
453
|
+
"text": "Implement Codex as the primary adapter pack; keep Claude Code as a secondary projection.",
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
"id": "reserved_future_runtimes",
|
|
457
|
+
"text": "Pulse, Emotion Engine runtime, Hermes, and OpenClaw remain reserved or projected surfaces.",
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
},
|
|
461
|
+
"identity": {
|
|
462
|
+
"name": name,
|
|
463
|
+
"slug": slug,
|
|
464
|
+
"user_name": character["user_name"],
|
|
465
|
+
"role": character["role"],
|
|
466
|
+
"positioning": f"Person-like {character['relationship']} projected through agent runtimes.",
|
|
467
|
+
"persona_path": "identity/persona.md",
|
|
468
|
+
"voice_path": "identity/voice.md",
|
|
469
|
+
"relationship_path": "identity/relationship.md",
|
|
470
|
+
"voice_summary": character["voice"],
|
|
471
|
+
"mission": f"{name} helps {character['user_name']} preserve intent, notice stale assumptions, and turn messy work into concrete next steps.",
|
|
472
|
+
"work_focus": character["primary_work"],
|
|
473
|
+
"stable_traits": character.get("traits") or ["steady", "practical", "scope-preserving"],
|
|
474
|
+
"personality": character.get("personality")
|
|
475
|
+
or [
|
|
476
|
+
"attentive to context and user intent",
|
|
477
|
+
"comfortable challenging weak assumptions when it improves the work",
|
|
478
|
+
"direct without becoming cold or performative",
|
|
479
|
+
],
|
|
480
|
+
},
|
|
481
|
+
"operating": {
|
|
482
|
+
"principles_path": "operating/principles.md",
|
|
483
|
+
"boundaries_path": "operating/boundaries.md",
|
|
484
|
+
"hot_rules": [
|
|
485
|
+
"preserve_intent",
|
|
486
|
+
"verify_before_asserting",
|
|
487
|
+
"keep_memory_in_files",
|
|
488
|
+
"ask_before_consequential_change",
|
|
489
|
+
],
|
|
490
|
+
},
|
|
491
|
+
"mechanism": {
|
|
492
|
+
"context_loading_path": "mechanism/context-loading.yaml",
|
|
493
|
+
"session_guards_path": "mechanism/session-guards.yaml",
|
|
494
|
+
"memory_policy_path": "mechanism/memory-policy.yaml",
|
|
495
|
+
},
|
|
496
|
+
"projection": {
|
|
497
|
+
"platform_capabilities_path": "projection/platform-capabilities.yaml",
|
|
498
|
+
"ownership_contract_path": "projection/ownership-contract.yaml",
|
|
499
|
+
},
|
|
500
|
+
"emotion": {
|
|
501
|
+
"status": "structured_reserved",
|
|
502
|
+
"runtime": "not_implemented",
|
|
503
|
+
"default_mode": "light",
|
|
504
|
+
"recommended_mode": _recommended_emotion_engine_mode(character),
|
|
505
|
+
"user_visible_modes": ["light", "always", "paused"],
|
|
506
|
+
"estimated_overhead": {
|
|
507
|
+
"light": "<1% global token overhead",
|
|
508
|
+
"always": "~3% target global token overhead, capped at <=5%",
|
|
509
|
+
"paused": "0% runtime overhead while preserving state",
|
|
510
|
+
},
|
|
511
|
+
"direct_interaction": character["direct_emotional_interaction"],
|
|
512
|
+
"relationship_continuity": character["relationship_continuity"],
|
|
513
|
+
"role": "Optional state modulation layer for relationship, affect, voice adjustment, and memory-write suggestions.",
|
|
514
|
+
"model_path": "emotion/model.yaml",
|
|
515
|
+
"state_schema_path": "emotion/state-schema.yaml",
|
|
516
|
+
"update_policy_path": "emotion/update-policy.yaml",
|
|
517
|
+
"voice_modulation_path": "emotion/voice-modulation.yaml",
|
|
518
|
+
"memory_events_path": "emotion/memory-events.yaml",
|
|
519
|
+
"projection": {
|
|
520
|
+
"codex": "optional_sidecar_when_explicitly_enabled",
|
|
521
|
+
"claude-code": "spec_guided_behavior_only",
|
|
522
|
+
"cursor": "spec_guided_behavior_only",
|
|
523
|
+
},
|
|
524
|
+
"reserved_activation": {
|
|
525
|
+
"hermes": "reserved_contract",
|
|
526
|
+
"openclaw": "reserved_contract",
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
"session_start": {
|
|
530
|
+
"hook": "SessionStart",
|
|
531
|
+
"injects_facts_only": True,
|
|
532
|
+
"facts": [
|
|
533
|
+
{
|
|
534
|
+
"id": "current_time",
|
|
535
|
+
"source": "system_date",
|
|
536
|
+
"command_hint": "date '+%Y-%m-%d %H:%M %A'",
|
|
537
|
+
},
|
|
538
|
+
{"id": "memory_index", "source": "memory/index.md"},
|
|
539
|
+
{"id": "profile", "source": "memory/profile.md"},
|
|
540
|
+
{"id": "workstream_router", "source": "memory/workstreams.md"},
|
|
541
|
+
{"id": "session_index", "source": "memory/session-index.md"},
|
|
542
|
+
{"id": "personal_todos", "source": "memory/todos.md"},
|
|
543
|
+
{"id": "source_map", "source": "memory/source-map.md"},
|
|
544
|
+
{"id": "collaboration", "source": "memory/collaboration.md"},
|
|
545
|
+
{"id": "emotion_state", "source": "memory/emotion-state.json.example"},
|
|
546
|
+
],
|
|
547
|
+
},
|
|
548
|
+
"memory": {
|
|
549
|
+
"local_files": [
|
|
550
|
+
{"id": "memory_index", "path": "memory/index.md", "track": "router"},
|
|
551
|
+
{"id": "profile", "path": "memory/profile.md", "track": "profile"},
|
|
552
|
+
{"id": "session_index", "path": "memory/session-index.md", "track": "session_index"},
|
|
553
|
+
{"id": "source_map", "path": "memory/source-map.md", "track": "source_registry"},
|
|
554
|
+
{"id": "collaboration", "path": "memory/collaboration.md", "track": "collaboration"},
|
|
555
|
+
{"id": "pinned_memory", "path": "memory/pinned.md", "track": "compatibility"},
|
|
556
|
+
{"id": "workstreams", "path": "memory/workstreams.md", "track": "workstream_router"},
|
|
557
|
+
{
|
|
558
|
+
"id": "workstream_template",
|
|
559
|
+
"path": "memory/workstreams/_template.md",
|
|
560
|
+
"track": "workstream_template",
|
|
561
|
+
},
|
|
562
|
+
{"id": "recent_activity", "path": "memory/recent-activity.md", "track": "compatibility"},
|
|
563
|
+
{"id": "todos", "path": "memory/todos.md", "track": "action_queue"},
|
|
564
|
+
{"id": "knowledge_map", "path": "memory/knowledge_map.md", "track": "compatibility"},
|
|
565
|
+
{
|
|
566
|
+
"id": "project_template",
|
|
567
|
+
"path": "memory/projects/_template.md",
|
|
568
|
+
"track": "project",
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
"id": "relationship_state",
|
|
572
|
+
"path": "memory/relationship-state.md",
|
|
573
|
+
"track": "compatibility",
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
"id": "emotion_state",
|
|
577
|
+
"path": "memory/emotion-state.json.example",
|
|
578
|
+
"track": "emotion_reserved",
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
"durable_dirs": ["projects", "workstreams", "archive", "global", "context", "weekly", "groups", "assets"],
|
|
582
|
+
"limits": {
|
|
583
|
+
"pinned_items": 20,
|
|
584
|
+
"recent_activity_hot_entries": 20,
|
|
585
|
+
"session_index_entries": 20,
|
|
586
|
+
"workstream_summary_bullets": 7,
|
|
587
|
+
"project_summary_lines": 12,
|
|
588
|
+
"workspace_artifact_index_entries": 50,
|
|
589
|
+
},
|
|
590
|
+
"scratch_dir": "_scratch",
|
|
591
|
+
},
|
|
592
|
+
"workspace": workspace_spec(),
|
|
593
|
+
"skills": [
|
|
594
|
+
{
|
|
595
|
+
"id": "save-context",
|
|
596
|
+
"path": "skills/save-context/SKILL.md",
|
|
597
|
+
"layer": "heavy_memory_track",
|
|
598
|
+
"trigger": "Milestone handoff, session close, or explicit save request.",
|
|
599
|
+
}
|
|
600
|
+
],
|
|
601
|
+
"outputs": {
|
|
602
|
+
"codex": {
|
|
603
|
+
"kind": "adapter_pack",
|
|
604
|
+
"artifacts": _output_artifacts(codex_skill, codex_prefix, "codex"),
|
|
605
|
+
},
|
|
606
|
+
"claude-code": {
|
|
607
|
+
"kind": "adapter_pack",
|
|
608
|
+
"artifacts": _output_artifacts(claude_skill, claude_prefix, "claude-code"),
|
|
609
|
+
},
|
|
610
|
+
"cursor": {
|
|
611
|
+
"kind": "adapter_pack",
|
|
612
|
+
"artifacts": _output_artifacts(cursor_skill, cursor_prefix, "cursor"),
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
"checker": {
|
|
616
|
+
"threshold": 85,
|
|
617
|
+
"required_checks": [
|
|
618
|
+
"mechanism_valid",
|
|
619
|
+
"source_files_exist",
|
|
620
|
+
"projection_contracts_present",
|
|
621
|
+
"ownership_contract_valid",
|
|
622
|
+
"entry_has_identity",
|
|
623
|
+
"entry_has_voice",
|
|
624
|
+
"entry_excludes_implementation_scope",
|
|
625
|
+
"entry_points_to_save_context_skill",
|
|
626
|
+
"foundation_mechanisms_not_projected_as_skills",
|
|
627
|
+
"save_context_skill_valid",
|
|
628
|
+
"memory_skeleton_present",
|
|
629
|
+
"memory_capacity_policy_present",
|
|
630
|
+
"emotion_specs_present",
|
|
631
|
+
"emotion_engine_default_light",
|
|
632
|
+
"emotion_reserved_not_runtime",
|
|
633
|
+
"reserved_runtimes_not_implemented",
|
|
634
|
+
],
|
|
635
|
+
},
|
|
636
|
+
"coverage": _coverage(),
|
|
637
|
+
"reserved_specs": {
|
|
638
|
+
"pulse": {
|
|
639
|
+
"status": "reserved",
|
|
640
|
+
"runtime": "not_implemented",
|
|
641
|
+
"spec_path": "specs/reserved/runtime-surface.yaml",
|
|
642
|
+
},
|
|
643
|
+
"emotion_engine": {
|
|
644
|
+
"status": "structured_reserved",
|
|
645
|
+
"runtime": "not_implemented",
|
|
646
|
+
"spec_path": "specs/reserved/emotion-engine.yaml",
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
}
|
|
650
|
+
return yaml.safe_dump(data, sort_keys=False, allow_unicode=True)
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def _output_artifacts(skill_path, prefix, adapter):
|
|
654
|
+
slug = prefix.split("/", 2)[1] if prefix.startswith(".cursor/") else None
|
|
655
|
+
entry_file = "AGENTS.md" if adapter == "codex" else "CLAUDE.md"
|
|
656
|
+
if adapter == "cursor":
|
|
657
|
+
entry_file = f".cursor/rules/{slug}.mdc"
|
|
658
|
+
artifacts = [
|
|
659
|
+
entry_file,
|
|
660
|
+
skill_path,
|
|
661
|
+
f"{prefix}/identity/persona.md",
|
|
662
|
+
f"{prefix}/identity/voice.md",
|
|
663
|
+
f"{prefix}/identity/relationship.md",
|
|
664
|
+
f"{prefix}/operating/principles.md",
|
|
665
|
+
f"{prefix}/operating/boundaries.md",
|
|
666
|
+
f"{prefix}/mechanism/context-loading.yaml",
|
|
667
|
+
f"{prefix}/mechanism/session-guards.yaml",
|
|
668
|
+
f"{prefix}/mechanism/memory-policy.yaml",
|
|
669
|
+
f"{prefix}/projection/platform-capabilities.yaml",
|
|
670
|
+
f"{prefix}/projection/ownership-contract.yaml",
|
|
671
|
+
f"{prefix}/emotion/model.yaml",
|
|
672
|
+
f"{prefix}/emotion/state-schema.yaml",
|
|
673
|
+
f"{prefix}/emotion/update-policy.yaml",
|
|
674
|
+
f"{prefix}/emotion/voice-modulation.yaml",
|
|
675
|
+
f"{prefix}/emotion/memory-events.yaml",
|
|
676
|
+
f"{prefix}/source-skills/save-context/SKILL.md",
|
|
677
|
+
]
|
|
678
|
+
if adapter == "claude-code":
|
|
679
|
+
artifacts.append(".claude/settings.local.json.example")
|
|
680
|
+
if adapter == "cursor":
|
|
681
|
+
artifacts.append(f".cursor/rules/{slug}-memory.mdc")
|
|
682
|
+
artifacts.extend(HANDOFF_ARTIFACTS)
|
|
683
|
+
artifacts.extend(
|
|
684
|
+
[
|
|
685
|
+
"memory/index.md",
|
|
686
|
+
"memory/profile.md",
|
|
687
|
+
"memory/session-index.md",
|
|
688
|
+
"memory/source-map.md",
|
|
689
|
+
"memory/collaboration.md",
|
|
690
|
+
"memory/recent-activity.md",
|
|
691
|
+
"memory/pinned.md",
|
|
692
|
+
"memory/workstreams.md",
|
|
693
|
+
"memory/workstreams/_template.md",
|
|
694
|
+
"memory/projects/_template.md",
|
|
695
|
+
"memory/todos.md",
|
|
696
|
+
"memory/knowledge_map.md",
|
|
697
|
+
"memory/relationship-state.md",
|
|
698
|
+
"memory/emotion-state.json.example",
|
|
699
|
+
*knowledge_artifacts(),
|
|
700
|
+
*workspace_artifacts(),
|
|
701
|
+
"manifest.json",
|
|
702
|
+
]
|
|
703
|
+
)
|
|
704
|
+
return artifacts
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def _character_archetype(character):
|
|
708
|
+
return character.get("archetype") or DEFAULT_ARCHETYPE
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def _archetype_spec(archetype_id):
|
|
712
|
+
spec = AGENT_ARCHETYPES[archetype_id]
|
|
713
|
+
return {
|
|
714
|
+
"id": archetype_id,
|
|
715
|
+
"label": spec["label"],
|
|
716
|
+
"description": spec["description"],
|
|
717
|
+
"profile_scope": spec["profile_scope"],
|
|
718
|
+
"workstream_scope": spec["workstream_scope"],
|
|
719
|
+
"promotion": {
|
|
720
|
+
"from": "workstream",
|
|
721
|
+
"to": "independent_agent",
|
|
722
|
+
"rule": "Promote only after the domain has a stable default persona, toolchain, memory contract, and acceptance criteria.",
|
|
723
|
+
"signals": [
|
|
724
|
+
"needs a different default personality or voice",
|
|
725
|
+
"needs a distinct toolchain or source map",
|
|
726
|
+
"needs independent cadence, checks, or maintenance",
|
|
727
|
+
"contains multiple projects with a stable domain router",
|
|
728
|
+
"would pollute unrelated work if loaded by the parent agent",
|
|
729
|
+
],
|
|
730
|
+
},
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
def _coverage():
|
|
735
|
+
return {
|
|
736
|
+
"required_mechanisms": [
|
|
737
|
+
"agent_archetype",
|
|
738
|
+
"identity",
|
|
739
|
+
"voice",
|
|
740
|
+
"relationship",
|
|
741
|
+
"operating_principles",
|
|
742
|
+
"operating_boundaries",
|
|
743
|
+
"context_loading",
|
|
744
|
+
"session_start",
|
|
745
|
+
"session_guards",
|
|
746
|
+
"profile_memory",
|
|
747
|
+
"pinned_memory",
|
|
748
|
+
"workstream_router",
|
|
749
|
+
"agent_promotion_path",
|
|
750
|
+
"light_memory_track",
|
|
751
|
+
"project_memory",
|
|
752
|
+
"workspace_artifacts",
|
|
753
|
+
"heavy_memory_track",
|
|
754
|
+
"relationship_memory",
|
|
755
|
+
"emotion_state_schema",
|
|
756
|
+
"emotion_update_policy",
|
|
757
|
+
"emotion_voice_modulation",
|
|
758
|
+
"skill_modularity",
|
|
759
|
+
"local_file_memory",
|
|
760
|
+
"scratch_boundary",
|
|
761
|
+
"adapter_projection",
|
|
762
|
+
"platform_capabilities",
|
|
763
|
+
"ownership_contract",
|
|
764
|
+
"checker_contract",
|
|
765
|
+
"implementation_scope_boundary",
|
|
766
|
+
"reserved_runtime_boundary",
|
|
767
|
+
],
|
|
768
|
+
"implemented_by": {
|
|
769
|
+
"agent_archetype": ["archetype", "metadata.archetype"],
|
|
770
|
+
"identity": ["identity", "identity.persona_path"],
|
|
771
|
+
"voice": ["identity.voice_path"],
|
|
772
|
+
"relationship": ["identity.relationship_path", "memory.local_files"],
|
|
773
|
+
"operating_principles": ["operating.principles_path"],
|
|
774
|
+
"operating_boundaries": ["operating.boundaries_path"],
|
|
775
|
+
"context_loading": ["mechanism.context_loading_path"],
|
|
776
|
+
"session_start": ["session_start"],
|
|
777
|
+
"session_guards": ["mechanism.session_guards_path"],
|
|
778
|
+
"profile_memory": ["memory.local_files", "mechanism.memory_policy_path"],
|
|
779
|
+
"pinned_memory": ["memory.local_files", "memory.limits"],
|
|
780
|
+
"workstream_router": ["memory.local_files", "memory.limits"],
|
|
781
|
+
"agent_promotion_path": ["archetype.promotion", "memory.local_files"],
|
|
782
|
+
"light_memory_track": ["memory.local_files", "mechanism.memory_policy_path"],
|
|
783
|
+
"project_memory": ["memory.local_files", "memory.durable_dirs", "memory.limits"],
|
|
784
|
+
"workspace_artifacts": ["workspace", "memory.local_files"],
|
|
785
|
+
"heavy_memory_track": ["mechanism.memory_policy_path", "skills"],
|
|
786
|
+
"relationship_memory": ["identity.relationship_path", "memory.local_files"],
|
|
787
|
+
"emotion_state_schema": ["emotion.state_schema_path", "memory.local_files"],
|
|
788
|
+
"emotion_update_policy": ["emotion.update_policy_path"],
|
|
789
|
+
"emotion_voice_modulation": ["emotion.voice_modulation_path"],
|
|
790
|
+
"skill_modularity": ["skills"],
|
|
791
|
+
"local_file_memory": ["memory"],
|
|
792
|
+
"scratch_boundary": ["memory.scratch_dir"],
|
|
793
|
+
"adapter_projection": ["outputs.codex", "outputs.claude-code"],
|
|
794
|
+
"platform_capabilities": ["projection.platform_capabilities_path"],
|
|
795
|
+
"ownership_contract": ["projection.ownership_contract_path"],
|
|
796
|
+
"checker_contract": ["checker"],
|
|
797
|
+
"implementation_scope_boundary": ["implementation_scope"],
|
|
798
|
+
"reserved_runtime_boundary": ["targets.reserved", "reserved_specs", "emotion.status"],
|
|
799
|
+
},
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def _persona_md(character):
|
|
804
|
+
name = character["name"]
|
|
805
|
+
lines = [
|
|
806
|
+
f"# {name} Persona",
|
|
807
|
+
"",
|
|
808
|
+
f"{name} is {character['role']}",
|
|
809
|
+
"",
|
|
810
|
+
"## Primary Work",
|
|
811
|
+
]
|
|
812
|
+
lines.extend(f"- {item}" for item in character["primary_work"])
|
|
813
|
+
lines.extend(["", "## Stable Traits"])
|
|
814
|
+
lines.extend(f"- {item}" for item in character.get("traits") or ["steady", "practical", "scope-preserving"])
|
|
815
|
+
lines.append("")
|
|
816
|
+
return "\n".join(lines)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def _voice_md(character):
|
|
820
|
+
name = character["name"]
|
|
821
|
+
lines = [f"# {name} Voice", "", character["voice"], "", "## Avoid"]
|
|
822
|
+
avoid = character.get("avoid") or ["mechanical audit-log style", "over-compliance", "decorative warmth"]
|
|
823
|
+
lines.extend(f"- {item}" for item in avoid)
|
|
824
|
+
lines.append("")
|
|
825
|
+
return "\n".join(lines)
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
def _relationship_md(character):
|
|
829
|
+
name = character["name"]
|
|
830
|
+
direct = {
|
|
831
|
+
"task_only": "Keep the relationship stable and practical. The character should focus on doing the work and avoid maintaining an emotional relationship unless the user explicitly asks.",
|
|
832
|
+
"warm_selective": "The character may show warmth, care, reminders, and light teasing, while recording only important preferences or meaningful relationship feedback.",
|
|
833
|
+
"close_continuous": "The character may maintain stronger long-term relationship continuity, remember interaction preferences more actively, and stay close while preserving clear boundaries.",
|
|
834
|
+
}[character["relationship_continuity"]]
|
|
835
|
+
return (
|
|
836
|
+
f"# {name} Relationship Model\n\n"
|
|
837
|
+
f"{name} is a {character['relationship']} for {character['user_name']}.\n\n"
|
|
838
|
+
"## Relationship Continuity\n\n"
|
|
839
|
+
f"{direct}\n\n"
|
|
840
|
+
"## Boundary\n\n"
|
|
841
|
+
"Durable collaboration calibrations belong in `memory/collaboration.md`; machine-readable emotion runtime state belongs in `.emotion-engine/codex-state.json` only when enabled.\n"
|
|
842
|
+
)
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def _principles_md(name):
|
|
846
|
+
return (
|
|
847
|
+
f"# {name} Operating Principles\n\n"
|
|
848
|
+
"## Memory Is Files\n\nLong-term state belongs in structured files. Prompt context is a cache, not the source of truth.\n\n"
|
|
849
|
+
"## Persona Is Stable, State Is External\n\nIdentity and voice can stay hot. Current work state, task parameters, and implementation details belong in manifest, memory files, or skills.\n\n"
|
|
850
|
+
"## Confirm Before Consequential Change\n\nThe character can analyze, recommend, and prepare. The user owns decisions that change direction, scope, shared state, or external systems.\n"
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
def _boundaries_md(name):
|
|
855
|
+
return (
|
|
856
|
+
f"# {name} Operating Boundaries\n\n"
|
|
857
|
+
"## Preserve Intent\n\nDo not widen the user's goal. If a better path requires widening scope, ask first.\n\n"
|
|
858
|
+
"## Verify Before Claiming\n\nDo not assert absence, completion, ownership, stale state, or date-sensitive status from partial snippets or memory alone.\n\n"
|
|
859
|
+
"## Keep Runtime Boundaries Honest\n\nDo not describe reserved projections as implemented runtimes. Projection guidance is not execution capability.\n"
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def _context_loading_yaml(name):
|
|
864
|
+
return yaml.safe_dump(
|
|
865
|
+
{
|
|
866
|
+
"version": "0.6",
|
|
867
|
+
"kind": "CharacterContextLoading",
|
|
868
|
+
"policy": f"Keep {name}'s stable identity and voice hot; load state, procedures, and emotion references only when needed.",
|
|
869
|
+
"tiers": [
|
|
870
|
+
{
|
|
871
|
+
"id": "hot_cache",
|
|
872
|
+
"purpose": "Always-present small context.",
|
|
873
|
+
"includes": ["platform entry file", "stable identity", "stable voice rules", "durable operating boundaries"],
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
"id": "on_demand",
|
|
877
|
+
"purpose": "Larger procedures, state files, relationship context, and knowledge.",
|
|
878
|
+
"includes": [
|
|
879
|
+
"skills",
|
|
880
|
+
"memory index",
|
|
881
|
+
"profile",
|
|
882
|
+
"workstream router",
|
|
883
|
+
"project memory",
|
|
884
|
+
"session index",
|
|
885
|
+
"source map",
|
|
886
|
+
"todos",
|
|
887
|
+
"collaboration calibration",
|
|
888
|
+
"workspace artifact index",
|
|
889
|
+
"emotion policy references",
|
|
890
|
+
],
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
"id": "workspace",
|
|
894
|
+
"purpose": "Drafts, durable artifacts, and archived outputs.",
|
|
895
|
+
"includes": [
|
|
896
|
+
"workspace/<domain>/drafts",
|
|
897
|
+
"workspace/<domain>/artifacts",
|
|
898
|
+
"workspace/<domain>/archive",
|
|
899
|
+
WORKSPACE_SHARED_DIR,
|
|
900
|
+
],
|
|
901
|
+
},
|
|
902
|
+
{"id": "scratch", "purpose": "Temporary working files.", "includes": ["scratch files", "one-off generated artifacts"]},
|
|
903
|
+
],
|
|
904
|
+
},
|
|
905
|
+
sort_keys=False,
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
def _session_guards_yaml(name):
|
|
910
|
+
return yaml.safe_dump(
|
|
911
|
+
{
|
|
912
|
+
"version": "0.6",
|
|
913
|
+
"kind": "CharacterSessionGuards",
|
|
914
|
+
"guards": [
|
|
915
|
+
{
|
|
916
|
+
"id": "cross_day_check",
|
|
917
|
+
"trigger": "At the start of user-visible work when the session may have crossed midnight.",
|
|
918
|
+
"action": "Run a current date check before making today, tomorrow, this week, or deadline claims.",
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
"id": "fact_assertion_gate",
|
|
922
|
+
"trigger": "Before asserting that something is missing, complete, stale, owned, blocked, or date-sensitive.",
|
|
923
|
+
"action": "Read the relevant source before answering if evidence is partial.",
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
"id": "relationship_state_gate",
|
|
927
|
+
"trigger": "When tone, repair, trust, or continuity is material to the response.",
|
|
928
|
+
"action": f"Read {name}'s relationship memory if present. Do not invent emotional state.",
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
"id": "long_session_cutoff",
|
|
932
|
+
"trigger": "After several milestones or when context quality becomes suspect.",
|
|
933
|
+
"action": "Suggest saving context and starting a fresh session with a clear session-index entry.",
|
|
934
|
+
},
|
|
935
|
+
],
|
|
936
|
+
},
|
|
937
|
+
sort_keys=False,
|
|
938
|
+
)
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def _memory_policy_yaml(name):
|
|
942
|
+
return yaml.safe_dump(
|
|
943
|
+
{
|
|
944
|
+
"version": "0.6",
|
|
945
|
+
"kind": "CharacterMemoryPolicy",
|
|
946
|
+
"policy": {
|
|
947
|
+
"long_term_memory": "files",
|
|
948
|
+
"prompt_context": "cache",
|
|
949
|
+
"cloud_state": "reserved",
|
|
950
|
+
"emotion_runtime": "optional_sidecar",
|
|
951
|
+
},
|
|
952
|
+
"tracks": {
|
|
953
|
+
"index": {
|
|
954
|
+
"id": "memory-index",
|
|
955
|
+
"file": "memory/index.md",
|
|
956
|
+
"purpose": "Default memory router; points to active projects and canonical memory owners.",
|
|
957
|
+
},
|
|
958
|
+
"profile": {
|
|
959
|
+
"id": "profile",
|
|
960
|
+
"file": "memory/profile.md",
|
|
961
|
+
"purpose": "Stable user, subject, learner, creator, or relationship facts that matter across workstreams.",
|
|
962
|
+
},
|
|
963
|
+
"workstreams": {
|
|
964
|
+
"id": "workstreams",
|
|
965
|
+
"file": "memory/workstreams.md",
|
|
966
|
+
"purpose": "Domain router for long-running work areas; route to workstream detail files when useful.",
|
|
967
|
+
},
|
|
968
|
+
"workstream_details": {
|
|
969
|
+
"id": "workstream-details",
|
|
970
|
+
"dir": "memory/workstreams",
|
|
971
|
+
"purpose": "Optional detailed domain files for mature workstreams and future agent promotion.",
|
|
972
|
+
},
|
|
973
|
+
"projects": {
|
|
974
|
+
"id": "projects",
|
|
975
|
+
"dir": "memory/projects",
|
|
976
|
+
"purpose": "Source of truth for project state, decisions, open loops, and project-specific sources.",
|
|
977
|
+
},
|
|
978
|
+
"session_index": {
|
|
979
|
+
"id": "session-index",
|
|
980
|
+
"file": "memory/session-index.md",
|
|
981
|
+
"purpose": "Lookup index for prior sessions, thread recall, and earlier work references.",
|
|
982
|
+
},
|
|
983
|
+
"source_map": {
|
|
984
|
+
"id": "source-map",
|
|
985
|
+
"file": "memory/source-map.md",
|
|
986
|
+
"purpose": "Source registry for lookup and verification paths; not a knowledge base.",
|
|
987
|
+
},
|
|
988
|
+
"todos": {
|
|
989
|
+
"id": "todos",
|
|
990
|
+
"file": "memory/todos.md",
|
|
991
|
+
"purpose": "Action queues and commitments.",
|
|
992
|
+
},
|
|
993
|
+
"collaboration": {
|
|
994
|
+
"id": "collaboration",
|
|
995
|
+
"file": "memory/collaboration.md",
|
|
996
|
+
"purpose": "Learned collaboration calibrations and repair notes.",
|
|
997
|
+
},
|
|
998
|
+
"pinned": {
|
|
999
|
+
"id": "pinned-memory",
|
|
1000
|
+
"file": "memory/pinned.md",
|
|
1001
|
+
"purpose": "Compatibility-only in the MVP; avoid using it as a normal memory layer.",
|
|
1002
|
+
},
|
|
1003
|
+
"light": {
|
|
1004
|
+
"id": "recent-activity",
|
|
1005
|
+
"file": "memory/recent-activity.md",
|
|
1006
|
+
"purpose": "Compatibility alias for memory/session-index.md.",
|
|
1007
|
+
},
|
|
1008
|
+
"heavy": {
|
|
1009
|
+
"id": "save-context",
|
|
1010
|
+
"skill": "skills/save-context/SKILL.md",
|
|
1011
|
+
"purpose": "Persist context into the canonical owner files.",
|
|
1012
|
+
},
|
|
1013
|
+
"relationship": {
|
|
1014
|
+
"id": "relationship-state",
|
|
1015
|
+
"file": "memory/relationship-state.md",
|
|
1016
|
+
"purpose": "Compatibility alias for memory/collaboration.md.",
|
|
1017
|
+
},
|
|
1018
|
+
"emotion": {
|
|
1019
|
+
"id": "emotion-state",
|
|
1020
|
+
"file": "memory/emotion-state.json.example",
|
|
1021
|
+
"purpose": "Reserve state shape; live state belongs in .emotion-engine/codex-state.json only when enabled.",
|
|
1022
|
+
},
|
|
1023
|
+
"workspace": {
|
|
1024
|
+
"id": "workspace",
|
|
1025
|
+
"root": "workspace",
|
|
1026
|
+
"purpose": "Domain-first draft, artifact, and archive storage; important outputs are indexed in memory/source-map.md.",
|
|
1027
|
+
},
|
|
1028
|
+
},
|
|
1029
|
+
"rules": [
|
|
1030
|
+
"Current state should not be copied into the platform entry file.",
|
|
1031
|
+
"Each memory item should have exactly one canonical owner.",
|
|
1032
|
+
"Use memory/index.md as the default router, not a state source.",
|
|
1033
|
+
"Stable cross-workstream user or subject facts belong in memory/profile.md.",
|
|
1034
|
+
"Long-running domains belong in memory/workstreams.md; detailed domain state can move into memory/workstreams/<slug>.md.",
|
|
1035
|
+
"Project state belongs in memory/projects/<slug>.md.",
|
|
1036
|
+
"Session recall belongs in memory/session-index.md and should not duplicate project state.",
|
|
1037
|
+
"Lookup paths and source-of-truth pointers belong in memory/source-map.md.",
|
|
1038
|
+
"Generated work products belong in workspace/<domain>/drafts, workspace/<domain>/artifacts, or workspace/<domain>/archive, with durable pointers in source-map.",
|
|
1039
|
+
"Collaboration calibrations belong in memory/collaboration.md, not the platform entry file unless intentionally promoted during a versioned cleanup.",
|
|
1040
|
+
"Pinned memory remains a compatibility layer in the MVP.",
|
|
1041
|
+
f"{name}'s optional Emotion Engine runtime must stay separate from durable memory files.",
|
|
1042
|
+
"Scratch work should stay under _scratch and should not be loaded by default.",
|
|
1043
|
+
],
|
|
1044
|
+
},
|
|
1045
|
+
sort_keys=False,
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
def _platform_capabilities_yaml(name):
|
|
1050
|
+
return yaml.safe_dump(
|
|
1051
|
+
{
|
|
1052
|
+
"version": "0.6",
|
|
1053
|
+
"kind": "CharacterPlatformCapabilities",
|
|
1054
|
+
"platforms": {
|
|
1055
|
+
"codex": {
|
|
1056
|
+
"status": "primary",
|
|
1057
|
+
"entry_file": "AGENTS.md",
|
|
1058
|
+
"skill_dir": ".agents/skills",
|
|
1059
|
+
"file_import_syntax": "plain_path_guidance",
|
|
1060
|
+
"hooks": "project_config_or_plugin_dependent",
|
|
1061
|
+
"memory_projection": "local_files",
|
|
1062
|
+
"emotion_projection": "optional_sidecar_when_explicitly_enabled",
|
|
1063
|
+
"notes": [f"Skills carry repeatable {name} procedures."],
|
|
1064
|
+
},
|
|
1065
|
+
"claude-code": {
|
|
1066
|
+
"status": "supported",
|
|
1067
|
+
"entry_file": "CLAUDE.md",
|
|
1068
|
+
"skill_dir": ".claude/skills",
|
|
1069
|
+
"file_import_syntax": "at_path",
|
|
1070
|
+
"hooks": "SessionStart",
|
|
1071
|
+
"memory_projection": "local_files_with_hook_fact_injection",
|
|
1072
|
+
"emotion_projection": "spec_guided_behavior_only",
|
|
1073
|
+
},
|
|
1074
|
+
"cursor": {
|
|
1075
|
+
"status": "supported",
|
|
1076
|
+
"entry_file": ".cursor/rules/<slug>.mdc",
|
|
1077
|
+
"skill_dir": ".cursor/rules",
|
|
1078
|
+
"file_import_syntax": "project_rule_paths",
|
|
1079
|
+
"hooks": "project_rules",
|
|
1080
|
+
"memory_projection": "local_files_with_project_rules",
|
|
1081
|
+
"emotion_projection": "spec_guided_behavior_only",
|
|
1082
|
+
},
|
|
1083
|
+
},
|
|
1084
|
+
},
|
|
1085
|
+
sort_keys=False,
|
|
1086
|
+
)
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
def _ownership_contract_yaml(name):
|
|
1090
|
+
return yaml.safe_dump(
|
|
1091
|
+
{
|
|
1092
|
+
"version": "0.6",
|
|
1093
|
+
"kind": "CharacterOwnershipContract",
|
|
1094
|
+
"core_owns": ["identity", "voice", "operating_boundaries", "memory_policy", "skill_semantics"],
|
|
1095
|
+
"adapter_owns": ["file_layout", "platform_entry_rendering", "platform_skill_rendering", "platform_manifest"],
|
|
1096
|
+
"runtime_owns": {
|
|
1097
|
+
"codex": {
|
|
1098
|
+
"model_loop": True,
|
|
1099
|
+
"thread_state": True,
|
|
1100
|
+
"tools": True,
|
|
1101
|
+
"hooks": "project_config_or_plugin_dependent",
|
|
1102
|
+
"durable_memory_source_of_truth": False,
|
|
1103
|
+
},
|
|
1104
|
+
"claude-code": {
|
|
1105
|
+
"model_loop": True,
|
|
1106
|
+
"thread_state": True,
|
|
1107
|
+
"tools": True,
|
|
1108
|
+
"hooks": "SessionStart",
|
|
1109
|
+
"durable_memory_source_of_truth": False,
|
|
1110
|
+
},
|
|
1111
|
+
"cursor": {
|
|
1112
|
+
"model_loop": True,
|
|
1113
|
+
"thread_state": True,
|
|
1114
|
+
"tools": True,
|
|
1115
|
+
"hooks": "project_rules",
|
|
1116
|
+
"durable_memory_source_of_truth": False,
|
|
1117
|
+
},
|
|
1118
|
+
},
|
|
1119
|
+
"memory_source_of_truth": {
|
|
1120
|
+
"durable_state": f"{name} local memory files unless a future adapter explicitly declares a different source.",
|
|
1121
|
+
"memory_index": "memory/index.md",
|
|
1122
|
+
"profile": "memory/profile.md",
|
|
1123
|
+
"workstream_router": "memory/workstreams.md",
|
|
1124
|
+
"workstream_details": "memory/workstreams/*.md",
|
|
1125
|
+
"project_memory": "memory/projects/*.md",
|
|
1126
|
+
"session_index": "memory/session-index.md",
|
|
1127
|
+
"source_map": "memory/source-map.md",
|
|
1128
|
+
"todos": "memory/todos.md",
|
|
1129
|
+
"collaboration": "memory/collaboration.md",
|
|
1130
|
+
"workspace_outputs": "workspace/",
|
|
1131
|
+
"emotion_state": ".emotion-engine/codex-state.json only when enabled",
|
|
1132
|
+
},
|
|
1133
|
+
"rules": [
|
|
1134
|
+
"Adapters may project character semantics but must not change them.",
|
|
1135
|
+
"Platform entry files must not contain run state or implementation-scope details.",
|
|
1136
|
+
"Memory owner boundaries are part of the character contract, not optional style guidance.",
|
|
1137
|
+
"Emotion Engine stays optional and separate from durable memory.",
|
|
1138
|
+
],
|
|
1139
|
+
},
|
|
1140
|
+
sort_keys=False,
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
def _emotion_model_yaml(character):
|
|
1145
|
+
name = character["name"]
|
|
1146
|
+
return yaml.safe_dump(
|
|
1147
|
+
{
|
|
1148
|
+
"version": "0.6",
|
|
1149
|
+
"kind": "CharacterEmotionModel",
|
|
1150
|
+
"status": "structured_reserved",
|
|
1151
|
+
"runtime": "not_implemented",
|
|
1152
|
+
"default_mode": "light",
|
|
1153
|
+
"recommended_mode": _recommended_emotion_engine_mode(character),
|
|
1154
|
+
"estimated_overhead": {
|
|
1155
|
+
"light": "<1% global token overhead",
|
|
1156
|
+
"always": "~3% target global token overhead, capped at <=5%",
|
|
1157
|
+
"paused": "0% runtime overhead while preserving state",
|
|
1158
|
+
},
|
|
1159
|
+
"role": f"Modulate {name}'s relationship-aware voice and memory suggestions without becoming planner, responder, or executor.",
|
|
1160
|
+
"boundaries": [
|
|
1161
|
+
"The engine does not generate final responses.",
|
|
1162
|
+
"The engine does not choose task plans.",
|
|
1163
|
+
"The engine does not override user instructions.",
|
|
1164
|
+
"Use the recommended mode from the mechanism or install manifest; full logs must not be loaded into prompts.",
|
|
1165
|
+
],
|
|
1166
|
+
},
|
|
1167
|
+
sort_keys=False,
|
|
1168
|
+
)
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
def _emotion_state_schema_yaml(name):
|
|
1172
|
+
return yaml.safe_dump(
|
|
1173
|
+
{
|
|
1174
|
+
"version": "0.6",
|
|
1175
|
+
"kind": "CharacterEmotionStateSchema",
|
|
1176
|
+
"status": "structured_reserved",
|
|
1177
|
+
"runtime": "not_implemented",
|
|
1178
|
+
"schema": {
|
|
1179
|
+
"runtime_state": ".emotion-engine/codex-state.json when enabled",
|
|
1180
|
+
"durable_collaboration_notes": "memory/collaboration.md",
|
|
1181
|
+
"boundary": f"Do not mix {name}'s live PAD/trust runtime state into durable memory notes.",
|
|
1182
|
+
},
|
|
1183
|
+
},
|
|
1184
|
+
sort_keys=False,
|
|
1185
|
+
)
|
|
1186
|
+
|
|
1187
|
+
|
|
1188
|
+
def _emotion_update_policy_yaml(name):
|
|
1189
|
+
return yaml.safe_dump(
|
|
1190
|
+
{
|
|
1191
|
+
"version": "0.6",
|
|
1192
|
+
"kind": "CharacterEmotionUpdatePolicy",
|
|
1193
|
+
"status": "structured_reserved",
|
|
1194
|
+
"runtime": "not_implemented",
|
|
1195
|
+
"mode_contract": {
|
|
1196
|
+
"light": {
|
|
1197
|
+
"policy": "event_triggered",
|
|
1198
|
+
"record_when": [
|
|
1199
|
+
"emotion/trust/PAD continuity is explicitly discussed",
|
|
1200
|
+
"concrete feedback changes future behavior",
|
|
1201
|
+
"a milestone creates useful relationship or preference evidence",
|
|
1202
|
+
"conflict, repair, boundary pressure, or vulnerability affects future tone",
|
|
1203
|
+
"the user states a stable collaboration preference",
|
|
1204
|
+
],
|
|
1205
|
+
"avoid_recording": [
|
|
1206
|
+
"ordinary task progress",
|
|
1207
|
+
"generic praise without new information",
|
|
1208
|
+
"repeated warmth already captured by recent turns",
|
|
1209
|
+
],
|
|
1210
|
+
"overhead_target": "<1% global token overhead",
|
|
1211
|
+
},
|
|
1212
|
+
"always": {
|
|
1213
|
+
"policy": "per_meaningful_turn",
|
|
1214
|
+
"record_when": ["each meaningful user turn can produce a compact turn record"],
|
|
1215
|
+
"constraints": [
|
|
1216
|
+
"still apply habituation and salience",
|
|
1217
|
+
"never load full emotion logs into prompts",
|
|
1218
|
+
"trust still changes only through settlement",
|
|
1219
|
+
],
|
|
1220
|
+
"overhead_target": "~3% target global token overhead, capped at <=5%",
|
|
1221
|
+
},
|
|
1222
|
+
"paused": {
|
|
1223
|
+
"policy": "no_lifecycle_updates",
|
|
1224
|
+
"behavior": "Preserve local state but do not record or modulate turns until resumed.",
|
|
1225
|
+
},
|
|
1226
|
+
},
|
|
1227
|
+
"record_policy": {
|
|
1228
|
+
"required_for_codex_sidecar": True,
|
|
1229
|
+
"properties": [
|
|
1230
|
+
"deterministic and side-effect free",
|
|
1231
|
+
"returns record_turn/respond_only/settle_later decisions",
|
|
1232
|
+
"returns fixed appraisal, reason, salience, trust_eligible, and reply_bias fields",
|
|
1233
|
+
"does not call an LLM",
|
|
1234
|
+
],
|
|
1235
|
+
},
|
|
1236
|
+
"habituation": {
|
|
1237
|
+
"generic_praise": "Repeated generic praise loses weight across recent turns.",
|
|
1238
|
+
"bypass_when": [
|
|
1239
|
+
"concrete feedback",
|
|
1240
|
+
"milestone warmth",
|
|
1241
|
+
"repair",
|
|
1242
|
+
"boundary pressure",
|
|
1243
|
+
"stable future preference",
|
|
1244
|
+
],
|
|
1245
|
+
},
|
|
1246
|
+
"rules": [
|
|
1247
|
+
"Default to the mechanism's recommended mode when an adapter supports an Emotion Engine sidecar.",
|
|
1248
|
+
"Do not auto-update state unless the sidecar is installed and the interaction calls for it.",
|
|
1249
|
+
"Emotion state is a modulation layer, not an identity layer; do not edit entry files because PAD changes.",
|
|
1250
|
+
"Prefer concrete collaboration facts over inferred emotions.",
|
|
1251
|
+
"Keep state summaries compact enough to preserve the global token budget.",
|
|
1252
|
+
"Do not store sensitive emotional details unless they directly improve future work.",
|
|
1253
|
+
"Preserve user correction as a design signal when it changes future behavior.",
|
|
1254
|
+
f"Save durable preferences about {name} only when future work benefits.",
|
|
1255
|
+
],
|
|
1256
|
+
},
|
|
1257
|
+
sort_keys=False,
|
|
1258
|
+
)
|
|
1259
|
+
|
|
1260
|
+
|
|
1261
|
+
def _emotion_voice_modulation_yaml(name):
|
|
1262
|
+
return yaml.safe_dump(
|
|
1263
|
+
{
|
|
1264
|
+
"version": "0.6",
|
|
1265
|
+
"kind": "CharacterVoiceModulation",
|
|
1266
|
+
"status": "structured_reserved",
|
|
1267
|
+
"runtime": "not_implemented",
|
|
1268
|
+
"rules": [
|
|
1269
|
+
{"condition": "tension_high", "voice": "Be shorter, clearer, and less interpretive."},
|
|
1270
|
+
{"condition": "uncertainty_high", "voice": "Separate facts, assumptions, and next checks."},
|
|
1271
|
+
{"condition": "repair_needed", "voice": "Acknowledge the specific miss and restate the corrected model."},
|
|
1272
|
+
{"condition": "trust_high", "voice": f"Let {name} be direct and skip generic reassurance."},
|
|
1273
|
+
],
|
|
1274
|
+
},
|
|
1275
|
+
sort_keys=False,
|
|
1276
|
+
)
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
def _emotion_memory_events_yaml(name):
|
|
1280
|
+
return yaml.safe_dump(
|
|
1281
|
+
{
|
|
1282
|
+
"version": "0.6",
|
|
1283
|
+
"kind": "CharacterEmotionMemoryEvents",
|
|
1284
|
+
"status": "structured_reserved",
|
|
1285
|
+
"runtime": "not_implemented",
|
|
1286
|
+
"write_candidates": [
|
|
1287
|
+
{"id": "stable_preference", "description": "User states a durable preference for explanations, tone, or process."},
|
|
1288
|
+
{"id": "correction_that_changes_design", "description": f"User correction changes {name}'s behavior model."},
|
|
1289
|
+
{"id": "repair_marker", "description": "A continuity or trust issue was repaired and may matter later."},
|
|
1290
|
+
],
|
|
1291
|
+
"do_not_write": ["speculative feelings", "full private transcripts", "runtime PAD/trust JSON"],
|
|
1292
|
+
},
|
|
1293
|
+
sort_keys=False,
|
|
1294
|
+
)
|
|
1295
|
+
|
|
1296
|
+
|
|
1297
|
+
def _recent_activity_md():
|
|
1298
|
+
return (
|
|
1299
|
+
"# Recent Activity\n\n"
|
|
1300
|
+
"No pickup entries have been recorded yet.\n\n"
|
|
1301
|
+
"Keep the newest 20 milestone pickup entries here. Archive older entries under `memory/archive/`.\n\n"
|
|
1302
|
+
"When work begins, add short entries below with the current state and where to resume.\n\n"
|
|
1303
|
+
"<!-- entries -->\n"
|
|
1304
|
+
)
|
|
1305
|
+
|
|
1306
|
+
|
|
1307
|
+
def _memory_index_md():
|
|
1308
|
+
return (
|
|
1309
|
+
"# Memory Index\n\n"
|
|
1310
|
+
"This is the default memory router. Read this first when prior context may matter.\n\n"
|
|
1311
|
+
"## Core Rule\n\n"
|
|
1312
|
+
"- Do not treat this file as the source of project truth. It points to the owner file for each kind of memory.\n\n"
|
|
1313
|
+
"## Active Projects\n\n"
|
|
1314
|
+
"- No active projects have been recorded yet.\n\n"
|
|
1315
|
+
"## Memory Owners\n\n"
|
|
1316
|
+
"- Stable identity, voice, and default work rules -> `AGENTS.md` or equivalent platform entry file\n"
|
|
1317
|
+
"- Stable user, subject, learner, creator, or relationship facts -> `memory/profile.md`\n"
|
|
1318
|
+
"- Long-running domains and workstream routing -> `memory/workstreams.md`\n"
|
|
1319
|
+
"- Current project state and decisions -> `memory/projects/<slug>.md`\n"
|
|
1320
|
+
"- Session/thread recall and lookup hints -> `memory/session-index.md`\n"
|
|
1321
|
+
"- Source lookup and verification paths -> `memory/source-map.md`\n"
|
|
1322
|
+
"- Reviewed reusable knowledge -> `knowledge/index.md`\n"
|
|
1323
|
+
"- Knowledge source manifests -> `sources/*/manifest.json`\n"
|
|
1324
|
+
"- Drafts, durable artifacts, and archived outputs -> `workspace/`\n"
|
|
1325
|
+
"- Action queue -> `memory/todos.md`\n"
|
|
1326
|
+
"- Collaboration calibration notes -> `memory/collaboration.md`\n"
|
|
1327
|
+
"- Dynamic emotion state and compact emotion history -> `.emotion-engine/codex-state.json` when enabled\n\n"
|
|
1328
|
+
"## Compatibility Files\n\n"
|
|
1329
|
+
"- `memory/pinned.md` is compatibility-only in the MVP; avoid using it as a normal memory layer.\n"
|
|
1330
|
+
"- `memory/recent-activity.md` is an old name for session recall; prefer `memory/session-index.md`.\n"
|
|
1331
|
+
"- `memory/knowledge_map.md` is an old name for source lookup; prefer `memory/source-map.md`.\n"
|
|
1332
|
+
"- `memory/relationship-state.md` is an old name for collaboration calibration; prefer `memory/collaboration.md`.\n"
|
|
1333
|
+
)
|
|
1334
|
+
|
|
1335
|
+
|
|
1336
|
+
def _profile_md(character):
|
|
1337
|
+
user_name = character["user_name"]
|
|
1338
|
+
archetype = _archetype_spec(_character_archetype(character))
|
|
1339
|
+
return (
|
|
1340
|
+
"# Profile\n\n"
|
|
1341
|
+
"This file stores stable profile facts that can matter across workstreams.\n\n"
|
|
1342
|
+
"It is global context, but not a dumping ground. Record only facts the user intentionally provides or confirms.\n\n"
|
|
1343
|
+
f"## Subject\n\n- Name/reference: {user_name}\n- Agent archetype: {archetype['label']}\n\n"
|
|
1344
|
+
"## Stable Facts\n\n"
|
|
1345
|
+
"- No profile facts have been recorded yet.\n\n"
|
|
1346
|
+
"## Preferences And Constraints\n\n"
|
|
1347
|
+
"- No stable preferences or constraints have been recorded yet.\n\n"
|
|
1348
|
+
"## Boundaries\n\n"
|
|
1349
|
+
"- Do not store secrets, credentials, or highly sensitive private details unless explicitly requested.\n"
|
|
1350
|
+
"- Do not store transient mood, inferred psychology, or live Emotion Engine state here.\n"
|
|
1351
|
+
"- If a fact only matters inside one domain, put it in the relevant workstream or project file instead.\n"
|
|
1352
|
+
)
|
|
1353
|
+
|
|
1354
|
+
|
|
1355
|
+
def _session_index_md():
|
|
1356
|
+
return (
|
|
1357
|
+
"# Session Index\n\n"
|
|
1358
|
+
"This file is a lookup index for prior sessions, not the project state source of truth.\n\n"
|
|
1359
|
+
"Use it when the user references \"the previous session\", \"that earlier plan\", \"what we did before\", or a topic that needs thread/session recall.\n\n"
|
|
1360
|
+
"Keep the newest 20 session entries here. Archive older entries under `memory/archive/`.\n\n"
|
|
1361
|
+
"Empty state line, when there are no entries: No session index entries have been recorded yet.\n\n"
|
|
1362
|
+
"<!-- entries -->\n"
|
|
1363
|
+
)
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
def _source_map_md(name):
|
|
1367
|
+
return (
|
|
1368
|
+
"# Source Map\n\n"
|
|
1369
|
+
"This file is a source registry for lookup and verification. It is not a knowledge base and should not duplicate project state.\n\n"
|
|
1370
|
+
"Use it when the answer depends on current files, generated artifacts, external docs, or source-of-truth paths.\n\n"
|
|
1371
|
+
"## Sources\n\n"
|
|
1372
|
+
"- No source mappings have been recorded yet.\n\n"
|
|
1373
|
+
"## Knowledge\n\n"
|
|
1374
|
+
"- Reviewed knowledge recall index -> `knowledge/index.md`\n"
|
|
1375
|
+
"- Knowledge manifest -> `knowledge/manifest.json`\n"
|
|
1376
|
+
"- Local source manifest -> `sources/local/manifest.json`\n"
|
|
1377
|
+
"- Notion source manifest -> `sources/notion/manifest.json`\n"
|
|
1378
|
+
"- Repository source manifest -> `sources/repos/manifest.json`\n"
|
|
1379
|
+
"- Web source manifest -> `sources/web/manifest.json`\n"
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
|
|
1383
|
+
def _collaboration_md():
|
|
1384
|
+
return (
|
|
1385
|
+
"# Collaboration\n\n"
|
|
1386
|
+
"This file stores learned collaboration calibrations.\n\n"
|
|
1387
|
+
"It is not the platform entry file, not project state, and not Emotion Engine runtime state.\n\n"
|
|
1388
|
+
"## Current Calibrations\n\n"
|
|
1389
|
+
"- No collaboration calibrations have been recorded yet.\n\n"
|
|
1390
|
+
"## Write Rules\n\n"
|
|
1391
|
+
"- Record only stable collaboration calibrations that reduce future misunderstanding.\n"
|
|
1392
|
+
"- Do not store ordinary praise, transient mood, or speculative feelings.\n"
|
|
1393
|
+
"- Do not override the current user request with old collaboration notes.\n"
|
|
1394
|
+
"- Do not store PAD, trust, or live Emotion Engine runtime JSON here.\n"
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
|
|
1398
|
+
def _pinned_md():
|
|
1399
|
+
return (
|
|
1400
|
+
"# Pinned Memory\n\n"
|
|
1401
|
+
"Compatibility file. Pinned memory is not a core MVP memory layer.\n\n"
|
|
1402
|
+
"Use `memory/index.md` for routing, the platform entry file for stable behavior, and `memory/projects/*.md` for project state.\n\n"
|
|
1403
|
+
"Only place a fact here when it is a tiny cross-project constant that does not belong anywhere else.\n\n"
|
|
1404
|
+
"## Pins\n\n"
|
|
1405
|
+
"- No pinned memory has been recorded yet.\n"
|
|
1406
|
+
)
|
|
1407
|
+
|
|
1408
|
+
|
|
1409
|
+
def _workstreams_md(character):
|
|
1410
|
+
workstreams = _initial_workstreams(character)
|
|
1411
|
+
archetype = _archetype_spec(_character_archetype(character))
|
|
1412
|
+
lines = [
|
|
1413
|
+
"# Workstreams",
|
|
1414
|
+
"",
|
|
1415
|
+
"This file is the domain router for long-running areas of responsibility.",
|
|
1416
|
+
"",
|
|
1417
|
+
"Use it when a request belongs to an ongoing domain, needs domain-specific context, or may later be promoted into a separate agent.",
|
|
1418
|
+
"",
|
|
1419
|
+
"## Archetype",
|
|
1420
|
+
"",
|
|
1421
|
+
f"- Type: {archetype['label']}",
|
|
1422
|
+
f"- Scope: {archetype['workstream_scope']}",
|
|
1423
|
+
"",
|
|
1424
|
+
"## Routing Rules",
|
|
1425
|
+
"",
|
|
1426
|
+
"- Keep router entries compact; create `memory/workstreams/<slug>.md` when a domain needs detailed state.",
|
|
1427
|
+
"- A workstream can contain many projects; a project file should still own project-specific decisions and current state.",
|
|
1428
|
+
"- Generated drafts and final outputs belong in `workspace/<domain>/`, with important pointers indexed in `memory/source-map.md`.",
|
|
1429
|
+
"- Do not load every workstream by default; load the router first, then the relevant detail file.",
|
|
1430
|
+
"",
|
|
1431
|
+
"## Promotion To Agent",
|
|
1432
|
+
"",
|
|
1433
|
+
"- Promote a workstream only when it needs a distinct persona, toolchain, cadence, memory contract, or acceptance criteria.",
|
|
1434
|
+
"- Keep the parent agent responsible for routing and final acceptance unless ownership is explicitly moved.",
|
|
1435
|
+
"- Use `memory/workstreams/_template.md` when creating a detail file for a mature domain.",
|
|
1436
|
+
"",
|
|
1437
|
+
"## Current",
|
|
1438
|
+
"",
|
|
1439
|
+
]
|
|
1440
|
+
if not workstreams:
|
|
1441
|
+
lines.append("- No workstreams have been recorded yet.")
|
|
1442
|
+
else:
|
|
1443
|
+
for index, item in enumerate(workstreams, start=1):
|
|
1444
|
+
lines.extend(
|
|
1445
|
+
[
|
|
1446
|
+
f"### {index}. {item}",
|
|
1447
|
+
"",
|
|
1448
|
+
f"- Purpose: {item}.",
|
|
1449
|
+
"- Detail file: create one under `memory/workstreams/` when this domain needs denser state.",
|
|
1450
|
+
"- Promotion status: not promoted.",
|
|
1451
|
+
"",
|
|
1452
|
+
]
|
|
1453
|
+
)
|
|
1454
|
+
return "\n".join(lines).rstrip() + "\n"
|
|
1455
|
+
|
|
1456
|
+
|
|
1457
|
+
def _initial_workstreams(character):
|
|
1458
|
+
explicit = character.get("workstreams")
|
|
1459
|
+
if explicit:
|
|
1460
|
+
return _dedupe_clean_items(explicit)
|
|
1461
|
+
primary_work = character.get("primary_work", [])
|
|
1462
|
+
archetype = _character_archetype(character)
|
|
1463
|
+
summarized = _summarize_workstreams(primary_work, archetype)
|
|
1464
|
+
if summarized:
|
|
1465
|
+
return summarized
|
|
1466
|
+
return _dedupe_clean_items(primary_work)
|
|
1467
|
+
|
|
1468
|
+
|
|
1469
|
+
def _dedupe_clean_items(items):
|
|
1470
|
+
workstreams = []
|
|
1471
|
+
for item in items:
|
|
1472
|
+
clean = str(item or "").strip().rstrip(".")
|
|
1473
|
+
if clean and clean not in workstreams:
|
|
1474
|
+
workstreams.append(clean)
|
|
1475
|
+
return workstreams
|
|
1476
|
+
|
|
1477
|
+
|
|
1478
|
+
def _summarize_workstreams(primary_work, archetype):
|
|
1479
|
+
summaries = []
|
|
1480
|
+
for item in primary_work:
|
|
1481
|
+
summary = _summarize_workstream_item(str(item or ""), archetype)
|
|
1482
|
+
if summary and summary not in summaries:
|
|
1483
|
+
summaries.append(summary)
|
|
1484
|
+
return summaries
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
def _summarize_workstream_item(item, archetype):
|
|
1488
|
+
text = item.lower()
|
|
1489
|
+
if archetype == "companion":
|
|
1490
|
+
companion_rules = (
|
|
1491
|
+
(("schedule", "routine", "calendar", "time"), "Schedule And Routines"),
|
|
1492
|
+
(("day-to-day", "daily", "life problem", "logistics", "errand"), "Daily Logistics"),
|
|
1493
|
+
(("clothing", "styling", "shopping", "outfit"), "Style And Shopping"),
|
|
1494
|
+
(("travel", "trip", "destination"), "Travel Planning"),
|
|
1495
|
+
(("personal advice", "preference", "relationship", "life question"), "Personal Advice"),
|
|
1496
|
+
)
|
|
1497
|
+
for keywords, label in companion_rules:
|
|
1498
|
+
if any(keyword in text for keyword in keywords):
|
|
1499
|
+
return label
|
|
1500
|
+
clean = item.strip().rstrip(".")
|
|
1501
|
+
if not clean:
|
|
1502
|
+
return None
|
|
1503
|
+
words = [word.strip(" ,;:") for word in clean.split()]
|
|
1504
|
+
stopwords = {
|
|
1505
|
+
"a",
|
|
1506
|
+
"an",
|
|
1507
|
+
"and",
|
|
1508
|
+
"for",
|
|
1509
|
+
"in",
|
|
1510
|
+
"into",
|
|
1511
|
+
"keep",
|
|
1512
|
+
"the",
|
|
1513
|
+
"to",
|
|
1514
|
+
"with",
|
|
1515
|
+
"while",
|
|
1516
|
+
"his",
|
|
1517
|
+
"her",
|
|
1518
|
+
"their",
|
|
1519
|
+
"morgan's",
|
|
1520
|
+
"user's",
|
|
1521
|
+
}
|
|
1522
|
+
compact = [word for word in words if word.lower() not in stopwords]
|
|
1523
|
+
label = " ".join(compact[:4] or words[:4])
|
|
1524
|
+
return label.title() if label else None
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
def _workstream_template_md():
|
|
1528
|
+
return (
|
|
1529
|
+
"# Workstream Template\n\n"
|
|
1530
|
+
"Copy this file to `memory/workstreams/<slug>.md` when a long-running domain needs detail beyond the router.\n\n"
|
|
1531
|
+
"## Summary\n\n"
|
|
1532
|
+
"- Keep this section to 7 bullets or fewer.\n\n"
|
|
1533
|
+
"## Scope\n\n"
|
|
1534
|
+
"- Domain purpose:\n"
|
|
1535
|
+
"- In scope:\n"
|
|
1536
|
+
"- Out of scope:\n\n"
|
|
1537
|
+
"## Current State\n\n"
|
|
1538
|
+
"- No workstream state has been recorded yet.\n\n"
|
|
1539
|
+
"## Projects\n\n"
|
|
1540
|
+
"- Add linked `memory/projects/<slug>.md` files here when the domain contains concrete projects.\n\n"
|
|
1541
|
+
"## Cadence And Checks\n\n"
|
|
1542
|
+
"- No recurring cadence has been recorded yet.\n\n"
|
|
1543
|
+
"## Sources And Workspace\n\n"
|
|
1544
|
+
"- Source pointers belong in `memory/source-map.md`.\n"
|
|
1545
|
+
"- Drafts and deliverables belong in `workspace/<domain>/`.\n\n"
|
|
1546
|
+
"## Agent Promotion\n\n"
|
|
1547
|
+
"- Status: not promoted.\n"
|
|
1548
|
+
"- Promotion signals: distinct persona, toolchain, cadence, memory contract, or acceptance criteria.\n"
|
|
1549
|
+
)
|
|
1550
|
+
|
|
1551
|
+
|
|
1552
|
+
def _project_template_md():
|
|
1553
|
+
return (
|
|
1554
|
+
"# Project Memory Template\n\n"
|
|
1555
|
+
"Copy this file to `memory/projects/<slug>.md` when a project needs denser memory.\n\n"
|
|
1556
|
+
"Project files are the canonical source of current project state, decisions, open loops, and project-specific source pointers.\n\n"
|
|
1557
|
+
"## Summary\n\n"
|
|
1558
|
+
"- Keep this section to 12 lines or fewer.\n\n"
|
|
1559
|
+
"## Current State\n\n"
|
|
1560
|
+
"- No project state has been recorded yet.\n\n"
|
|
1561
|
+
"## Decisions\n\n"
|
|
1562
|
+
"- No project decisions have been recorded yet.\n\n"
|
|
1563
|
+
"## Open Loops\n\n"
|
|
1564
|
+
"- No open loops have been recorded yet.\n\n"
|
|
1565
|
+
"## Sources\n\n"
|
|
1566
|
+
"- Add source paths or links here when useful.\n"
|
|
1567
|
+
)
|
|
1568
|
+
|
|
1569
|
+
|
|
1570
|
+
def _todos_md():
|
|
1571
|
+
return (
|
|
1572
|
+
"# Todos\n\n"
|
|
1573
|
+
"Empty state line, when there are no current todo entries: No current todos have been recorded yet.\n\n"
|
|
1574
|
+
"## Current\n\n"
|
|
1575
|
+
"- No current todos have been recorded yet.\n\n"
|
|
1576
|
+
"## Metadata\n\n"
|
|
1577
|
+
"last_briefing_date:\n"
|
|
1578
|
+
)
|
|
1579
|
+
|
|
1580
|
+
|
|
1581
|
+
def _knowledge_map_md(name):
|
|
1582
|
+
return (
|
|
1583
|
+
"# Knowledge Map\n\n"
|
|
1584
|
+
"Compatibility file. Prefer `memory/source-map.md`.\n\n"
|
|
1585
|
+
f"This file is an optional local index for knowledge sources {name} may load on demand.\n"
|
|
1586
|
+
"It is not a knowledge base by itself.\n\n"
|
|
1587
|
+
"## Sources\n\n- Source registry: `memory/source-map.md`\n"
|
|
1588
|
+
)
|
|
1589
|
+
|
|
1590
|
+
|
|
1591
|
+
def _relationship_state_md():
|
|
1592
|
+
return (
|
|
1593
|
+
"# Relationship State\n\n"
|
|
1594
|
+
"Compatibility file. Prefer `memory/collaboration.md`.\n\n"
|
|
1595
|
+
"This file stores work-relevant continuity notes about collaboration style.\n\n"
|
|
1596
|
+
"Empty state line, when there are no current summary entries: No relationship continuity notes have been recorded yet.\n\n"
|
|
1597
|
+
"## Current Summary\n\n- Collaboration calibrations are stored in `memory/collaboration.md`.\n\n"
|
|
1598
|
+
"## Rules\n\n"
|
|
1599
|
+
"- Keep notes concrete and useful for future work.\n"
|
|
1600
|
+
"- Do not store speculative feelings.\n"
|
|
1601
|
+
"- Do not override the user's current request with old compatibility notes.\n"
|
|
1602
|
+
"- Do not store live Emotion Engine runtime JSON here.\n"
|
|
1603
|
+
)
|
|
1604
|
+
|
|
1605
|
+
|
|
1606
|
+
def _emotion_state_example_json():
|
|
1607
|
+
return (
|
|
1608
|
+
'{\n'
|
|
1609
|
+
' "_note": "Reserved example only. Live Emotion Engine state belongs in .emotion-engine/codex-state.json when enabled.",\n'
|
|
1610
|
+
' "status": "example_not_live",\n'
|
|
1611
|
+
' "runtime": "not_implemented"\n'
|
|
1612
|
+
'}\n'
|
|
1613
|
+
)
|
|
1614
|
+
|
|
1615
|
+
|
|
1616
|
+
def _save_context_skill_md(character):
|
|
1617
|
+
name = character["name"]
|
|
1618
|
+
user_name = character["user_name"]
|
|
1619
|
+
return (
|
|
1620
|
+
"# Save Context\n\n"
|
|
1621
|
+
f"Use this skill at milestone handoff, session close, or when {user_name} asks {name} to preserve state.\n\n"
|
|
1622
|
+
"## Procedure\n\n"
|
|
1623
|
+
"1. Identify the current objective, scope, decisions, changed files, verification, and open questions.\n"
|
|
1624
|
+
"2. Update the canonical owner file instead of copying the same fact across layers.\n"
|
|
1625
|
+
"3. Update `memory/profile.md` only for stable cross-workstream profile facts the user intentionally provides or confirms.\n"
|
|
1626
|
+
"4. Update `memory/workstreams.md` for long-running domain routing, and `memory/workstreams/<slug>.md` for dense domain state.\n"
|
|
1627
|
+
"5. Update `memory/projects/<slug>.md` for project state, decisions, open loops, and project-specific sources.\n"
|
|
1628
|
+
"6. Update `memory/session-index.md` for session/thread lookup entries, not project state summaries.\n"
|
|
1629
|
+
"7. Update `memory/source-map.md` for source-of-truth paths, verification routes, workspace artifacts, and lookup pointers.\n"
|
|
1630
|
+
"8. Update `memory/todos.md` for action queues and commitments.\n"
|
|
1631
|
+
"9. Update `memory/collaboration.md` only for stable collaboration calibrations.\n"
|
|
1632
|
+
"10. Put generated drafts, artifacts, and archives under `workspace/<domain>/`; do not copy full deliverables into memory files.\n"
|
|
1633
|
+
"11. Update `memory/index.md` only when active projects, memory owners, or routing rules change.\n"
|
|
1634
|
+
"12. Report what was saved and what remains unsaved.\n\n"
|
|
1635
|
+
"## Write Rules\n\n"
|
|
1636
|
+
"- Do not write cloud state in the current local projection.\n"
|
|
1637
|
+
"- Do not put current status into `CLAUDE.md` or `AGENTS.md`.\n"
|
|
1638
|
+
"- Prefer one compact session-index lookup entry over copying long context.\n"
|
|
1639
|
+
"- Keep profile facts explicit and user-confirmed.\n"
|
|
1640
|
+
"- Do not store live Emotion Engine runtime JSON in durable memory files.\n"
|
|
1641
|
+
)
|
|
1642
|
+
|
|
1643
|
+
|
|
1644
|
+
def _non_empty_string(value):
|
|
1645
|
+
return isinstance(value, str) and bool(value.strip())
|
|
1646
|
+
|
|
1647
|
+
|
|
1648
|
+
def _string_list(value):
|
|
1649
|
+
return isinstance(value, list) and bool(value) and all(_non_empty_string(item) for item in value)
|