personacc 0.3.0__tar.gz

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.
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: personacc
3
+ Version: 0.3.0
4
+ Summary: Persona Compiler-Compiler — compiles a persona bundle (description blocks + attention chains + ≥1 chain-of-reasoning that uses them) and seats it into make_gba
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: chaincompiler-accc==0.3.0
7
+ Requires-Dist: corcc==0.3.0
8
+ Requires-Dist: chaincompiler==0.3.0
@@ -0,0 +1,81 @@
1
+ # PersonaCC — Persona Compiler-Compiler
2
+
3
+ The **persona** compiler of the cognition-chain stack: `ACCC → CORCC → PersonaCC`.
4
+
5
+ ## The ratified definition
6
+
7
+ > **A persona = a specific set of DESCRIPTION BLOCKS + a set of ACs + at least one
8
+ > CoR that USES them.**
9
+
10
+ ACCC forges an **attention chain** (inner, silent — *how to think*). CORCC forges a
11
+ **chain-of-reasoning** (outer, spoken — the persona-melt gauge). PersonaCC forges
12
+ the whole **persona**: the description blocks that say who the agent is, the
13
+ attention chains it thinks along, and the one-or-more CoRs that use them. Its
14
+ source language is the 2023 SDNA-template block grammar (`[KIND] … [/KIND]`).
15
+
16
+ ## The bundle (`notation.py`)
17
+
18
+ ```python
19
+ Block(kind="role", body="You are …") # kind is a FREE string — new kinds are legal
20
+ PersonaBundle(
21
+ name="…",
22
+ blocks=(Block(...), …), # description blocks (may be empty)
23
+ acs=("[Focus] ⇒ [Focus] ⇒ |Held|", …), # attention chains (accc notation)
24
+ cors=(persona_spec, …), # ≥1 corcc.PersonaSpec (the reasoning core)
25
+ blurb="…",
26
+ )
27
+ ```
28
+
29
+ `PersonaSpec` is really the **COR SPEC** (a half-persona — the spoken move-chain);
30
+ PersonaCC builds the full persona *above* it. New block kinds are legal by design
31
+ (the template-template rule): the grammar never closes over a fixed enum.
32
+
33
+ ## The *CC shape (`forge.py`)
34
+
35
+ - **`forge_bundle(bundle, db=…)`** — forge each AC via ACCC, each CoR via CORCC;
36
+ run the notation lint + the uses-them gate. Returns a `ForgedBundle`.
37
+ - **`lint(bundle)`** — notation/structure lint: every AC is a well-formed rune
38
+ chain, every CoR chain parses, ≥1 CoR, blocks well-formed.
39
+ - **`uses_them(bundle, strict=False)`** — the check that makes "…that USES them"
40
+ mechanical: every CoR move name must be a focus in a bundle AC **or** the CoR's
41
+ own derived chain. The escape hatch (a CoR's own derived chain always contains
42
+ its own moves) means a well-formed bundle never hard-fails in the default mode —
43
+ uncovered moves surface as **warnings** ("declared but not wired to an AC").
44
+ `strict=True` drops the escape hatch: an uncovered move is a **violation**.
45
+ (Weakened to a warning by default because the seed/proof bundles' real attention
46
+ atoms legitimately don't re-list every CoR move.)
47
+ - **`package(bundle, out_dir)`** — compile to `<name>/SKILL.md` (blocks as
48
+ sections + the AC set + each CoR's PRIME) via `chaincompiler.write_skill`.
49
+ - **`seat(bundle)`** — return exactly the kwargs
50
+ `chainaios.make_gba(domain, root, **seat(bundle))` accepts:
51
+ `{atoms, blurb, role_block, persona}`. Only `cors[0]` is seated today; seating
52
+ additional CoRs is future work.
53
+
54
+ ## TOTAL SYNTAXIFICATION (the doctrine)
55
+
56
+ One notation all the way down: **every persona block can be made with AC syntax, and
57
+ natural language never appears free — only as a component the specific AC syntax
58
+ licenses** (e.g. a condition gloss inside a focus). Blocks therefore carry a **grade**
59
+ (`Block.grade`): `prose` (soup — free NL, tolerated *only* for migration) → `chain`
60
+ (code — real chain syntax). Detection is honest — for the default `rune` language it
61
+ uses accc's **real parser** (`parse_ac`), not a regex; a body is a chain iff it parses.
62
+ The **melt-gauge** that CORCC runs on a CoR (forge the language, gate the text, drift =
63
+ melting) now runs on the whole persona: `forge.lint_blocks(bundle)` gates every
64
+ chain-graded block (a hard issue if it melts) and warns on every prose block
65
+ ("unsyntactified block: <kind>"). A malformed chain-block fires the same gate a melting
66
+ CoR does. The endpoint: the melt-gauge covers the entire persona, not just its CoRs.
67
+
68
+ **TYPE ⊥ SYNTAX.** A block is a triple — (type-`kind`, `lang`, `body` string): the
69
+ label is orthogonal to the language is orthogonal to the string. The typed content is
70
+ canonical; **every string is a *render*** of it in some chain language, and
71
+ re-rendering one typed corpus across languages IS the de-lore/masking mechanism —
72
+ myth-lang and research-lang are two render targets of the same typed persona. A
73
+ language is lintable once its melt-gauge is registered (`register_lang_gate(name, fn)`;
74
+ `LANG_GATES` holds `rune` by default); until then its blocks grade `prose` ("no gate
75
+ registered for lang=<x>"). To emit a conformant string, the LLM needs only the type
76
+ label + which syntax.
77
+
78
+ ## The library
79
+
80
+ `personapack` is the LIBRARY of pre-bound `PersonaBundle`s (Bodhisattva, Aegis,
81
+ SamayaReviewer, …), each with an honest activation grade and a receipt.
@@ -0,0 +1,13 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "personacc"
7
+ version = "0.3.0"
8
+ description = "Persona Compiler-Compiler — compiles a persona bundle (description blocks + attention chains + ≥1 chain-of-reasoning that uses them) and seats it into make_gba"
9
+ requires-python = ">=3.11"
10
+ dependencies = ["chaincompiler-accc==0.3.0", "corcc==0.3.0", "chaincompiler==0.3.0"]
11
+
12
+ [tool.setuptools.packages.find]
13
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,32 @@
1
+ """PersonaCC — Persona Compiler-Compiler. The syntax compiler for a PERSONA.
2
+
3
+ THE RATIFIED DEFINITION: a persona = a set of DESCRIPTION BLOCKS + a set of ACs +
4
+ at least one CoR that USES them. PersonaCC compiles that bundle: catch the
5
+ notation → lint (form + "uses them") → compile to a markdown body → package as
6
+ `<name>/SKILL.md` → SEAT into `chainaios.make_gba(persona=…)`. Composes ACCC (the
7
+ attention chains) + CORCC (the chains of reasoning). ACCC → CORCC → PersonaCC.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ __version__ = "0.3.0"
12
+
13
+ from .notation import Block, PersonaBundle
14
+ from .forge import (
15
+ ForgedBundle,
16
+ LANG_GATES,
17
+ forge_bundle,
18
+ lint,
19
+ lint_blocks,
20
+ package,
21
+ register_lang_gate,
22
+ render_blocks,
23
+ role_block,
24
+ seat,
25
+ uses_them,
26
+ )
27
+
28
+ __all__ = [
29
+ "Block", "PersonaBundle", "ForgedBundle",
30
+ "forge_bundle", "lint", "lint_blocks", "uses_them", "package", "seat",
31
+ "render_blocks", "role_block", "register_lang_gate", "LANG_GATES", "__version__",
32
+ ]
@@ -0,0 +1,390 @@
1
+ """PersonaCC — the Persona Compiler-Compiler. Composes ACCC + CORCC.
2
+
3
+ Same *CC shape as ACCC / CORCC / SCCC: catch the notation, LINT it (so the model
4
+ never emits a malformed persona), compile it to a markdown body, and PACKAGE that
5
+ as `<name>/SKILL.md`. PersonaCC judges no content — only the SYNTAX and the
6
+ STRUCTURE of the bundle (the ratified definition: blocks + ACs + ≥1 CoR that uses
7
+ them).
8
+
9
+ Where it sits: ACCC forges an attention chain; CORCC forges a CoR (embedding an
10
+ AC); PersonaCC forges the whole PERSONA (a set of blocks + ACs + ≥1 CoR that uses
11
+ them) and SEATS it into `chainaios.make_gba(persona=…)`. ACCC → CORCC → PersonaCC.
12
+
13
+ The headline check is `uses_them` — the lint that makes the "…that USES them"
14
+ clause of the definition mechanically checkable. See its docstring for the rule
15
+ and the honest weakening.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass, field
20
+ from pathlib import Path
21
+ import tempfile
22
+
23
+ from accc import Language as ACLanguage
24
+ from accc import forge as ac_forge
25
+ from accc import gate as ac_gate
26
+ from accc import parse_ac
27
+ from corcc import Persona, cor_chain, forge_persona, inner_template, prime as cor_prime
28
+ from chaincompiler import slugify, write_skill # made FROM CC (CC re-exports APE's write_skill/slugify)
29
+
30
+ from .notation import Block, PersonaBundle
31
+
32
+
33
+ # ── notation helpers ─────────────────────────────────────────────────────────
34
+ def _focus_set(chains: list[str]) -> set[str]:
35
+ """Every focus (ordered `[Focus]` term AND the `|Held|`) across the chains."""
36
+ foci: set[str] = set()
37
+ for ch in chains:
38
+ try:
39
+ ordered, held = parse_ac(ch)
40
+ except Exception:
41
+ continue # malformed chains are caught by `lint`, not here
42
+ foci.update(ordered)
43
+ if held:
44
+ foci.add(held)
45
+ return foci
46
+
47
+
48
+ def _derived_focus_set(bundle: PersonaBundle) -> set[str]:
49
+ """The union of every CoR's OWN derived chain foci (= all its move names)."""
50
+ return _focus_set([cor_chain(cor) for cor in bundle.cors])
51
+
52
+
53
+ # ── the notation lint (syntax + structure) ───────────────────────────────────
54
+ def lint(bundle: PersonaBundle) -> list[str]:
55
+ """Syntax/structure lint of a persona bundle's NOTATION (the *CC check).
56
+
57
+ Checks, without judging any content:
58
+ - every AC string is a well-formed rune attention chain (parseable, has a
59
+ bounded `|Held|`);
60
+ - every CoR's derived chain is well-formed;
61
+ - the bundle has ≥1 CoR and every block has a non-empty kind.
62
+
63
+ Returns a list of human-readable issue strings (empty = clean notation). This
64
+ is the "is it well-formed?" gate; the "does the CoR use the ACs?" gate is the
65
+ separate `uses_them` below.
66
+ """
67
+ issues: list[str] = []
68
+ if not bundle.cors:
69
+ issues.append("bundle has no CoR (a persona needs ≥1 CoR)")
70
+ for i, ac in enumerate(bundle.acs):
71
+ try:
72
+ _, held = parse_ac(ac)
73
+ except Exception as e:
74
+ issues.append(f"AC[{i}] {ac!r}: not a well-formed attention chain ({e})")
75
+ continue
76
+ if not held:
77
+ issues.append(f"AC[{i}] {ac!r}: no bounded |Held| convergence")
78
+ for cor in bundle.cors:
79
+ chain = cor_chain(cor)
80
+ try:
81
+ parse_ac(chain)
82
+ except Exception as e:
83
+ issues.append(f"CoR {cor.name!r} derived chain {chain!r}: malformed ({e})")
84
+ for b in bundle.blocks:
85
+ if not b.kind.strip():
86
+ issues.append("a block has an empty kind")
87
+ # TOTAL SYNTAXIFICATION: a chain-graded block that MELTS its own gauge is a hard
88
+ # notation issue (folded in here). Prose blocks are only WARNINGS (soup is tolerated
89
+ # for migration), so they ride the warnings channel of `lint_blocks` — folding them
90
+ # in as issues would wrongly fail every migration-era bundle.
91
+ block_issues, _block_warnings = lint_blocks(bundle)
92
+ issues.extend(block_issues)
93
+ return issues
94
+
95
+
96
+ # ── the language-gate registry (TYPE ⊥ SYNTAX) ───────────────────────────────
97
+ # A block is (type, LANGUAGE, string). Each chain LANGUAGE carries its own melt-gauge:
98
+ # a callable `gate(body: str) -> list[str]` returning violation strings (empty = clean).
99
+ # "rune" is the built-in honeyc Dense Rune-Chain gauge; new languages register here.
100
+
101
+ def _rune_gate(body: str) -> list[str]:
102
+ """The 'rune' language gate = the block melt-gauge (the SAME gauge CORCC uses on a CoR).
103
+
104
+ CORCC forges an accc Language from a CoR's canonical `cor_chain(spec)` and gates the
105
+ spoken CoR text against it (`corcc.lint` = `ac_gate(persona.inner, cor_text)`); drift
106
+ out of the syntax = the persona "melting". The block analogue: the block IS its own
107
+ notation, so we forge the Language from the block's CLEAN rune skeleton (the canonical
108
+ chain `parse_ac` recovers) and gate the RAW body against it. A clean chain equals its
109
+ skeleton → 0 violations; a body that hides prose between foci, misplaces the `|Held|`,
110
+ or drops a `⇒` drifts from the skeleton → the gate fires. The forge needs a scratch DB
111
+ (the gate is only defined against a forged Language — the forge-backed check, stated
112
+ plainly), so a throwaway temp DB is used and discarded per call.
113
+ """
114
+ foci, held = parse_ac(body) # body is rune-chain-graded, so this succeeds
115
+ canonical = " ⇒ ".join(f"[{f}]" for f in foci)
116
+ if held:
117
+ canonical += f" ⇒ |{held}|"
118
+ with tempfile.TemporaryDirectory() as tmp:
119
+ language = ac_forge("rune-block-gate", [canonical], db=str(Path(tmp) / "rune-gate.db"))
120
+ violations, _warnings = ac_gate(language, body)
121
+ if violations:
122
+ return [f"{len(violations)} violation(s) vs its rune skeleton {canonical!r}"]
123
+ return []
124
+
125
+
126
+ LANG_GATES: dict[str, callable] = {"rune": _rune_gate}
127
+
128
+
129
+ def register_lang_gate(name: str, fn) -> None:
130
+ """Register a melt-gauge for a chain LANGUAGE: `fn(body: str) -> list[str]` (violations).
131
+
132
+ TYPE ⊥ SYNTAX: registering a gate makes a language LINTABLE — blocks written in it
133
+ then grade "chain" and are melt-gauged like any rune block. Until a language has a
134
+ gate it is unlintable = soup-grade (its blocks grade "prose")."""
135
+ LANG_GATES[name] = fn
136
+
137
+
138
+ def lint_blocks(bundle: PersonaBundle) -> tuple[list[str], list[str]]:
139
+ """Block-level syntaxification gate → `(issues, warnings)`, dispatched by `lang`.
140
+
141
+ For each description block, split on its `grade`:
142
+ - "chain" → run the language's registered melt-gauge (`LANG_GATES[block.lang]`, the
143
+ same gauge CORCC uses on a CoR). A chain block that drifts out of its language's
144
+ syntax is a hard ISSUE.
145
+ - "prose" → a WARNING: "unsyntactified block: <kind>" for an unparsed rune body, or
146
+ "no gate registered for lang=<x>" for a block in an unknown (unlintable) language.
147
+ Tolerated soup, but flagged so the migration to one-notation-all-the-way-down is
148
+ visible.
149
+
150
+ Mirrors `uses_them`'s (violations, warnings) split.
151
+ """
152
+ issues: list[str] = []
153
+ warnings: list[str] = []
154
+ for b in bundle.blocks:
155
+ if b.grade == "chain":
156
+ gate = LANG_GATES.get(b.lang)
157
+ if gate is None: # defensive: grade==chain implies a gate exists, but be safe
158
+ warnings.append(f"no gate registered for lang={b.lang}")
159
+ continue
160
+ for v in gate(b.body):
161
+ issues.append(f"block {b.kind!r} (lang={b.lang}) MELTS the gate: {v}")
162
+ elif b.lang != "rune" and b.lang not in LANG_GATES:
163
+ warnings.append(f"no gate registered for lang={b.lang}")
164
+ else:
165
+ warnings.append(f"unsyntactified block: {b.kind}")
166
+ return issues, warnings
167
+
168
+
169
+ # ── the "…that USES them" lint (the headline check) ──────────────────────────
170
+ def uses_them(bundle: PersonaBundle, *, strict: bool = False) -> tuple[list[str], list[str]]:
171
+ """Check the "…that USES them" clause of the persona definition.
172
+
173
+ THE RULE (as ratified):
174
+ Every CoR move name must appear as a FOCUS in at least one of the bundle's
175
+ AC chains, OR in the CoR's OWN derived chain (`corcc.cor_chain(cor)`).
176
+
177
+ Honest implementation note (the weakening, stated loudly). A CoR's own derived
178
+ chain is `[m0] ⇒ [m1] ⇒ … ⇒ |m_last|` — so it ALWAYS contains every one of
179
+ that CoR's move names. Taken literally, the "OR the CoR's own derived chain"
180
+ escape hatch means a well-formed bundle can NEVER hard-fail. That is by design
181
+ for the seed/proof bundles, whose ACs (their real attention atoms) legitimately
182
+ do NOT re-list every CoR move — failing them would be wrong. So:
183
+
184
+ - DEFAULT (`strict=False`): the escape hatch is ON. A move grounded by a real
185
+ bundle AC is clean; a move grounded ONLY by its CoR's own derived chain is
186
+ a WARNING ("declared but not wired to an attention chain"); a move grounded
187
+ by NEITHER is a hard VIOLATION (only reachable via a structurally broken
188
+ spec). This is the "weaken to a warning and SAY SO" mode.
189
+
190
+ - STRICT (`strict=True`): the escape hatch is OFF. A move must be a focus in
191
+ a real bundle AC or it is a VIOLATION. This is the teeth: it fires on any
192
+ bundle whose CoR is not actually wired to its attention chains.
193
+
194
+ Returns `(violations, warnings)` — lists of human-readable strings.
195
+ """
196
+ ac_foci = _focus_set(list(bundle.acs))
197
+ violations: list[str] = []
198
+ warnings: list[str] = []
199
+ for cor in bundle.cors:
200
+ own_foci = _focus_set([cor_chain(cor)])
201
+ for move in cor.moves:
202
+ n = move.name
203
+ if n in ac_foci:
204
+ continue # grounded by a real bundle AC — fully wired
205
+ if strict:
206
+ violations.append(
207
+ f"{cor.name}.{n}: CoR move is not a focus in any bundle AC (strict)")
208
+ elif n in own_foci:
209
+ warnings.append(
210
+ f"{cor.name}.{n}: grounded only by the CoR's own derived chain, "
211
+ "not surfaced as a focus in any bundle AC")
212
+ else:
213
+ violations.append(
214
+ f"{cor.name}.{n}: move appears in neither the bundle ACs nor the "
215
+ "CoR's own derived chain (structurally broken)")
216
+ return violations, warnings
217
+
218
+
219
+ # ── forge ────────────────────────────────────────────────────────────────────
220
+ @dataclass
221
+ class ForgedBundle:
222
+ """The compiled artifacts of a persona bundle."""
223
+ bundle: PersonaBundle
224
+ ac_languages: list[ACLanguage] # one forged AC language per bundle AC (via ACCC)
225
+ personas: list[Persona] # one forged persona per CoR (via CORCC)
226
+ uses_violations: list[str] = field(default_factory=list)
227
+ uses_warnings: list[str] = field(default_factory=list)
228
+ notation_issues: list[str] = field(default_factory=list) # includes any melted chain-block issues
229
+ block_warnings: list[str] = field(default_factory=list) # prose (unsyntactified) blocks — tolerated
230
+
231
+ @property
232
+ def name(self) -> str:
233
+ return self.bundle.name
234
+
235
+ @property
236
+ def clean(self) -> bool:
237
+ return not self.notation_issues and not self.uses_violations
238
+
239
+
240
+ def forge_bundle(bundle: PersonaBundle, *, db: str, strict_uses: bool = False) -> ForgedBundle:
241
+ """Forge a persona bundle: forge each AC via ACCC, forge each CoR via CORCC.
242
+
243
+ - Each AC string is forged into its own `accc` Language (ACCC → the `ac:` kind).
244
+ - Each CoR (`corcc.PersonaSpec`) is forged into a `corcc.Persona` (which itself
245
+ forges the CoR's inner attention chain — CORCC composes ACCC).
246
+ - The bundle is linted (`lint`) for notation validity and gated by `uses_them`
247
+ for the "…that uses them" clause.
248
+
249
+ `strict_uses` selects the `uses_them` mode (see its docstring). This never
250
+ raises on a lint failure — it records the issues on the `ForgedBundle` so the
251
+ caller decides; that mirrors the other *CC (lint reports, it does not throw).
252
+ """
253
+ notation_issues = lint(bundle) # already includes melted chain-block issues
254
+ _block_issues, block_warnings = lint_blocks(bundle) # recover the prose-block warnings
255
+ ac_languages = [
256
+ ac_forge(f"{slugify(bundle.name)}-ac{i}", [ac], db=db)
257
+ for i, ac in enumerate(bundle.acs)
258
+ ]
259
+ personas = [forge_persona(cor, db=db) for cor in bundle.cors]
260
+ violations, warnings = uses_them(bundle, strict=strict_uses)
261
+ return ForgedBundle(
262
+ bundle=bundle, ac_languages=ac_languages, personas=personas,
263
+ uses_violations=violations, uses_warnings=warnings, notation_issues=notation_issues,
264
+ block_warnings=block_warnings,
265
+ )
266
+
267
+
268
+ def _safe_prime(persona: Persona) -> str:
269
+ """CORCC's persona PRIME, with a cue-free fallback.
270
+
271
+ Some CoR specs recovered from the proof GBAs have no per-move `cues` persisted
272
+ (the original build scripts weren't retained — honest under-specification, not
273
+ invented). CORCC's cue-based paragraph skeleton can't render those, so fall
274
+ back to a cue-free prime (inner chain + the ordered moves)."""
275
+ try:
276
+ return cor_prime(persona)
277
+ except Exception:
278
+ spec = persona.spec
279
+ moves = " → ".join(m.name for m in spec.moves)
280
+ return "\n".join([
281
+ f'# PRIME — persona "{spec.name}"',
282
+ f"({spec.blurb})" if spec.blurb else "",
283
+ "",
284
+ "## Inner attention chain (use silently)",
285
+ inner_template(persona),
286
+ "",
287
+ "## Chain-of-reasoning moves (perform in order; the last is the held convergence)",
288
+ moves,
289
+ "",
290
+ "_(per-move cue phrases were not persisted for this recovered persona.)_",
291
+ ])
292
+
293
+
294
+ # ── render / package ─────────────────────────────────────────────────────────
295
+ def render_blocks(bundle: PersonaBundle) -> str:
296
+ """The description blocks rendered as markdown sections (one `##` per block)."""
297
+ out: list[str] = []
298
+ for b in bundle.blocks:
299
+ title = b.kind.replace("_", " ").strip().title()
300
+ out.append(f"## {title}")
301
+ out.append("")
302
+ out.append(b.body.strip())
303
+ out.append("")
304
+ return "\n".join(out).strip()
305
+
306
+
307
+ def role_block(bundle: PersonaBundle) -> str | None:
308
+ """Assemble a CLAUDE.md-ready role block from the description blocks.
309
+
310
+ Returns None when the bundle carries no blocks (a degenerate persona whose
311
+ identity is its CoR — `make_gba` then derives the role from the persona moves).
312
+ """
313
+ if not bundle.blocks:
314
+ return None
315
+ header = f"# {bundle.name}"
316
+ if bundle.blurb:
317
+ header += f"\n\n{bundle.blurb}"
318
+ body = render_blocks(bundle)
319
+ return f"{header}\n\n{body}".strip()
320
+
321
+
322
+ def package(bundle: PersonaBundle, out_dir: str, *, db: str | None = None,
323
+ description: str | None = None) -> Path:
324
+ """Compile the persona bundle to a publishable `<name>/SKILL.md` package.
325
+
326
+ The body renders: the description blocks as sections + the AC set + each CoR's
327
+ re-injectable PRIME (from CORCC). Written via `chaincompiler.write_skill`.
328
+ A `db` is needed to forge each CoR's prime; if omitted a temp DB is used.
329
+ """
330
+ desc = description or (
331
+ f"Persona (blocks + attention chains + chain-of-reasoning): {bundle.blurb}."
332
+ if bundle.blurb else f"Persona bundle {bundle.name}."
333
+ )
334
+
335
+ def _build(db_path: str) -> str:
336
+ personas = [forge_persona(cor, db=db_path) for cor in bundle.cors]
337
+ parts: list[str] = [
338
+ "This is a **persona** — a set of DESCRIPTION BLOCKS, a set of ATTENTION CHAINS "
339
+ "(how to think), and one or more CHAINS OF REASONING (spoken, ending in a decision) "
340
+ "that use them. Adopt the blocks; think along the attention chains; perform the CoR.",
341
+ "",
342
+ ]
343
+ blocks_md = render_blocks(bundle)
344
+ if blocks_md:
345
+ parts += ["# Description blocks", "", blocks_md, ""]
346
+ parts += ["# Attention chains (how to think — silent templates)", ""]
347
+ if bundle.acs:
348
+ parts.append("```rune")
349
+ parts += [ac.strip() for ac in bundle.acs]
350
+ parts.append("```")
351
+ else:
352
+ parts.append("_(none — this persona's chains are carried by its CoR)_")
353
+ parts.append("")
354
+ parts += ["# Chains of reasoning (spoken; you MUST perform these, in order)", ""]
355
+ for p in personas:
356
+ parts.append(_safe_prime(p))
357
+ parts.append("")
358
+ return "\n".join(parts).rstrip()
359
+
360
+ if db is None:
361
+ with tempfile.TemporaryDirectory() as tmp:
362
+ body = _build(str(Path(tmp) / "personacc.db"))
363
+ else:
364
+ body = _build(db)
365
+ return write_skill(bundle.name, desc, body, out_dir=out_dir, extra={"kind": "persona-bundle"})
366
+
367
+
368
+ # ── seat — the make_gba seam ─────────────────────────────────────────────────
369
+ def seat(bundle: PersonaBundle) -> dict:
370
+ """Return exactly the kwargs `chainaios.make_gba(domain, root, **seat(bundle))` accepts.
371
+
372
+ {
373
+ "atoms": list(bundle.acs), # the attention chains become the GBA's atoms
374
+ "blurb": bundle.blurb,
375
+ "role_block": role_block(bundle), # rendered from the description blocks (or None)
376
+ "persona": bundle.cors[0], # the first CoR is seated as the GBA's CoR
377
+ }
378
+
379
+ FUTURE WORK: only `cors[0]` is seated. A GBA seats exactly one CoR persona
380
+ today (the melt-gauge is one ordered move-chain), so multi-CoR bundles have
381
+ their remaining CoRs compiled into the SKILL.md by `package` but not seated.
382
+ Seating additional CoRs (e.g. as sub-personas / a routed ensemble) is future
383
+ work — it needs a `make_gba` that carries more than one persona seam.
384
+ """
385
+ return {
386
+ "atoms": list(bundle.acs),
387
+ "blurb": bundle.blurb,
388
+ "role_block": role_block(bundle),
389
+ "persona": bundle.cors[0],
390
+ }
@@ -0,0 +1,129 @@
1
+ """PersonaCC notation — the PersonaBundle: what a persona actually IS.
2
+
3
+ THE RATIFIED DEFINITION (the author):
4
+
5
+ a persona = a specific set of DESCRIPTION BLOCKS
6
+ + a set of ACs (attention chains)
7
+ + at least one CoR that USES them.
8
+
9
+ PersonaCC is the SYNTAX compiler for that bundle. Its source language is the 2023
10
+ SDNA-template block grammar: a persona prompt is a stack of `[KIND] … [/KIND]`
11
+ DESCRIPTION BLOCKS (role, description, approach, wrapper, invocation-terms, …)
12
+ wrapped around a reasoning core. Here that core is made rigorous: the reasoning
13
+ core is one-or-more `corcc.PersonaSpec` CoRs, each grounded in a set of `accc`
14
+ attention chains.
15
+
16
+ Two atoms compose UP into a bundle:
17
+ - `Block` — one description block. `kind` is a free string; NEW block
18
+ kinds are legal by design (the template-template rule: the
19
+ block grammar is extensible, not a closed enum).
20
+ - `PersonaBundle` — the whole persona: blocks + ACs + ≥1 CoR + a blurb.
21
+
22
+ Note on the type below CORCC's `PersonaSpec`: that type is really the *COR SPEC*
23
+ (a half-persona — the spoken reasoning move-chain only). PersonaCC does NOT rename
24
+ it; it builds the FULL persona ABOVE it, adding the description blocks and the
25
+ attention chains the CoR is meant to use.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ from dataclasses import dataclass
30
+
31
+ from accc import parse_ac # accc's REAL chain parser — used for the honest is_chain check
32
+ from corcc.notation import PersonaSpec # NOTE: really the COR SPEC (half-persona). Do not rename.
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class Block:
37
+ """One description block of a persona prompt (an SDNA-template block).
38
+
39
+ TYPE ⊥ SYNTAX (the author's law): a block is really a triple — (type-`kind`, `lang`,
40
+ `body` string). The TYPE (what the block IS, e.g. "wrapper") is orthogonal to the
41
+ SYNTAX it is rendered in; the same block type renders differently in different
42
+ chain LANGUAGES (one uses only `⇒`, another `-> * + =` in sequence). The `body`
43
+ string is one RENDER of the canonical typed content in a chosen language.
44
+
45
+ - `kind` is a FREE STRING (e.g. "role", "description", "approach", "wrapper",
46
+ "invocation_terms" — or anything new). New kinds are legal by design; the
47
+ block grammar is a template-template, so it never closes over a fixed enum.
48
+ - `lang` names the chain language the `body` is written in. "rune" (the default)
49
+ is the honeyc Dense Rune-Chain that accc parses/gates. Other languages are
50
+ lintable only once a gate is registered for them (`forge.register_lang_gate`).
51
+ - `body` is the block's string (a chain in `lang`, or — soup-grade — prose).
52
+ """
53
+ kind: str
54
+ body: str
55
+ lang: str = "rune"
56
+
57
+ def __post_init__(self) -> None:
58
+ if not self.kind or not self.kind.strip():
59
+ raise ValueError("Block.kind must be a non-empty string")
60
+
61
+ @property
62
+ def is_chain(self) -> bool:
63
+ """True iff this block's body is a conformant chain IN ITS OWN `lang`.
64
+
65
+ Keyed to `lang`:
66
+ - "rune" — HONEST DETECTION via accc's REAL parser (not a regex). accc
67
+ exposes `parse_ac`, which runs the HoneyC parser and raises unless the
68
+ body contains a well-formed attention chain (`[Focus] ⇒ … ⇒ |Held|`).
69
+ That clean parse-check exists, so we use it directly; the
70
+ forge-in-a-throwaway-db fallback is NOT needed and is deliberately not
71
+ used. (Broad `except` because a non-chain body can trip the parser with
72
+ parser-internal errors, not only ValueError.)
73
+ - any OTHER lang — we cannot parse an arbitrary language, so it is a chain
74
+ iff a gate is REGISTERED for that lang (an unknown language is unlintable
75
+ = soup-grade). The gate itself (the melt-gauge) runs later in
76
+ `forge.lint_blocks`.
77
+ """
78
+ if self.lang == "rune":
79
+ try:
80
+ parse_ac(self.body)
81
+ return True
82
+ except Exception:
83
+ return False
84
+ from .forge import LANG_GATES # lazy: forge imports notation; avoid a cycle
85
+ return self.lang in LANG_GATES
86
+
87
+ @property
88
+ def grade(self) -> str:
89
+ """The syntaxification grade of this block:
90
+
91
+ - "chain" — the body is real chain syntax in its `lang` (code-grade; melt-gaugeable).
92
+ - "prose" — free natural language / an unlintable language (soup-grade; TOLERATED
93
+ for migration only).
94
+
95
+ This is the TOTAL-SYNTAXIFICATION gauge at the block level: every persona block
96
+ should eventually grade "chain" (one notation all the way down); NL is licensed
97
+ only as a component the syntax admits, never as a free-standing block.
98
+ """
99
+ return "chain" if self.is_chain else "prose"
100
+
101
+
102
+ @dataclass(frozen=True)
103
+ class PersonaBundle:
104
+ """A persona: DESCRIPTION BLOCKS + ACs + ≥1 CoR that uses them.
105
+
106
+ - `name` : the persona's name (also the SKILL.md slug).
107
+ - `blocks` : the description blocks (may be empty for a degenerate persona
108
+ whose identity is carried entirely by its CoR — e.g. the seeds).
109
+ - `acs` : attention-chain strings in the shared Dense-Rune notation
110
+ (`[Focus] ⇒ [Focus] ⇒ |Held|`), each parseable by `accc`.
111
+ - `cors` : one or more `corcc.PersonaSpec` CoRs. ≥1 is REQUIRED — a persona
112
+ with no reasoning core is not a persona.
113
+ - `blurb` : one-line description.
114
+ """
115
+ name: str
116
+ blocks: tuple[Block, ...]
117
+ acs: tuple[str, ...]
118
+ cors: tuple[PersonaSpec, ...]
119
+ blurb: str = ""
120
+
121
+ def __post_init__(self) -> None:
122
+ if not self.cors:
123
+ raise ValueError(
124
+ f"PersonaBundle {self.name!r} needs ≥1 CoR — a persona = description "
125
+ "blocks + ACs + at least one CoR that uses them (the ratified definition)."
126
+ )
127
+
128
+ def block_kinds(self) -> tuple[str, ...]:
129
+ return tuple(b.kind for b in self.blocks)
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: personacc
3
+ Version: 0.3.0
4
+ Summary: Persona Compiler-Compiler — compiles a persona bundle (description blocks + attention chains + ≥1 chain-of-reasoning that uses them) and seats it into make_gba
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: chaincompiler-accc==0.3.0
7
+ Requires-Dist: corcc==0.3.0
8
+ Requires-Dist: chaincompiler==0.3.0
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/personacc/__init__.py
4
+ src/personacc/forge.py
5
+ src/personacc/notation.py
6
+ src/personacc.egg-info/PKG-INFO
7
+ src/personacc.egg-info/SOURCES.txt
8
+ src/personacc.egg-info/dependency_links.txt
9
+ src/personacc.egg-info/requires.txt
10
+ src/personacc.egg-info/top_level.txt
11
+ tests/test_personacc.py
@@ -0,0 +1,3 @@
1
+ chaincompiler-accc==0.3.0
2
+ corcc==0.3.0
3
+ chaincompiler==0.3.0
@@ -0,0 +1 @@
1
+ personacc
@@ -0,0 +1,142 @@
1
+ """PersonaCC tests: bundle validity, the uses-them lint, notation lint, package, seat.
2
+
3
+ Self-contained — uses corcc seed CoRs (no personapack dependency). The
4
+ "passes on BODHISATTVA" half of the uses-them requirement is tested in
5
+ personapack (where BODHISATTVA lives)."""
6
+ from __future__ import annotations
7
+
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ from corcc import cor_chain
13
+ from corcc.notation import EINSTEIN, BANDIT
14
+
15
+ import personacc as pc
16
+ from personacc import Block, PersonaBundle
17
+
18
+
19
+ # ── a helper: a well-wired bundle whose acs = the CoR's own derived chain ──────
20
+ def _wired(spec=EINSTEIN, blocks=()):
21
+ return PersonaBundle(name=spec.name, blocks=blocks, acs=(cor_chain(spec),),
22
+ cors=(spec,), blurb=spec.blurb)
23
+
24
+
25
+ def test_bundle_requires_at_least_one_cor():
26
+ with pytest.raises(ValueError):
27
+ PersonaBundle(name="Empty", blocks=(), acs=("[A] ⇒ [B] ⇒ |C|",), cors=(), blurb="")
28
+
29
+
30
+ def test_block_kind_is_extensible():
31
+ # ANY kind string is legal (the template-template rule) — no closed enum.
32
+ b = Block(kind="a-brand-new-kind", body="hi")
33
+ assert b.kind == "a-brand-new-kind"
34
+ with pytest.raises(ValueError):
35
+ Block(kind=" ", body="x")
36
+
37
+
38
+ def test_blocks_render_as_sections():
39
+ bundle = _wired(blocks=(Block("role", "You are X."), Block("invocation_terms", "a · b")))
40
+ md = pc.render_blocks(bundle)
41
+ assert "## Role" in md and "You are X." in md
42
+ assert "## Invocation Terms" in md and "a · b" in md
43
+ rb = pc.role_block(bundle)
44
+ assert rb is not None and "# ThinkLikeEinstein" in rb
45
+
46
+
47
+ def test_role_block_none_when_no_blocks():
48
+ assert pc.role_block(_wired(blocks=())) is None
49
+
50
+
51
+ def test_notation_lint_catches_malformed_ac():
52
+ good = _wired()
53
+ assert pc.lint(good) == []
54
+ broken = PersonaBundle(name="Bad", blocks=(), acs=("this is not a rune chain",),
55
+ cors=(BANDIT,), blurb="")
56
+ issues = pc.lint(broken)
57
+ assert issues, "a malformed AC string should be flagged by the notation lint"
58
+
59
+
60
+ def test_uses_them_clean_when_acs_cover_moves():
61
+ # acs = the CoR's own derived chain -> every move is a focus in a real AC.
62
+ v, w = pc.uses_them(_wired())
63
+ assert v == [] and w == []
64
+
65
+
66
+ def test_uses_them_fires_on_deliberately_broken_bundle():
67
+ # acs do NOT cover the CoR's moves -> strict fires as VIOLATIONS, default as WARNINGS.
68
+ broken = PersonaBundle(name="Broken", blocks=(),
69
+ acs=("[Alpha] ⇒ [Beta] ⇒ |Gamma|",), cors=(EINSTEIN,), blurb="")
70
+ v_strict, _ = pc.uses_them(broken, strict=True)
71
+ assert v_strict, "strict uses-them must fire on a CoR not wired to any AC"
72
+ v_default, w_default = pc.uses_them(broken) # weakened mode
73
+ assert v_default == [] and w_default, "default mode reports warnings, not violations"
74
+
75
+
76
+ def test_package_writes_skill_md(tmp_path: Path):
77
+ path = pc.package(_wired(blocks=(Block("role", "You are Einstein."),)), out_dir=str(tmp_path))
78
+ assert path.name == "SKILL.md"
79
+ text = path.read_text()
80
+ assert "persona-bundle" in text # frontmatter kind
81
+ assert "Attention chains" in text # the AC set section
82
+ assert "PRIME" in text # each CoR's prime is rendered
83
+ assert cor_chain(EINSTEIN) in text # the CoR chain is present
84
+
85
+
86
+ def test_seat_returns_make_gba_kwargs():
87
+ kw = pc.seat(_wired(blocks=(Block("role", "You are Einstein."),)))
88
+ assert set(kw) == {"atoms", "blurb", "role_block", "persona"}
89
+ assert kw["atoms"] == [cor_chain(EINSTEIN)]
90
+ assert kw["persona"] is EINSTEIN
91
+ assert kw["role_block"] is not None
92
+
93
+
94
+ # ── block grades + the block melt-gauge (TOTAL SYNTAXIFICATION) ────────────────
95
+ def test_block_grades_chain_and_prose():
96
+ chain = Block("wrapper", "[Output] ⇒ [Adorn] ⇒ |Wrapped|")
97
+ assert chain.is_chain and chain.grade == "chain"
98
+ prose = Block("role", "You are a helpful wrapper.")
99
+ assert not prose.is_chain and prose.grade == "prose"
100
+
101
+
102
+ def test_lint_blocks_chain_clean_prose_warns():
103
+ bundle = _wired(blocks=(Block("wrapper", "[Output] ⇒ [Adorn] ⇒ |Wrapped|"),
104
+ Block("role", "You are X.")))
105
+ issues, warnings = pc.lint_blocks(bundle)
106
+ assert issues == [] # the chain block lints clean
107
+ assert any("unsyntactified block: role" in w for w in warnings)
108
+ # a clean chain block + prose warnings ⇒ lint() stays empty (prose is tolerated soup).
109
+ assert pc.lint(bundle) == []
110
+
111
+
112
+ def test_lint_blocks_malformed_chain_fires_gate():
113
+ bad = _wired(blocks=(Block("wrapper", "[Output] ⇒ [Adorn] this then blah ⇒ |Wrapped|"),))
114
+ assert bad.blocks[0].grade == "chain" # it still parses as a chain…
115
+ issues, _ = pc.lint_blocks(bad)
116
+ assert issues, "a malformed chain-block must fire its melt-gauge"
117
+ assert pc.lint(bad), "…and the melted-block issue folds into lint()"
118
+
119
+
120
+ def test_unknown_lang_grades_prose_until_gate_registered():
121
+ # TYPE ⊥ SYNTAX: an unregistered language is unlintable = soup-grade, until a gate lands.
122
+ from personacc.forge import LANG_GATES, register_lang_gate
123
+ b = Block("wrapper", "A -> B -> C", lang="arrowlang")
124
+ assert b.grade == "prose" # no gate registered yet
125
+ _, warnings = pc.lint_blocks(_wired(blocks=(b,)))
126
+ assert any("no gate registered for lang=arrowlang" in w for w in warnings)
127
+ try:
128
+ register_lang_gate("arrowlang", lambda body: [] if "->" in body else ["missing ->"])
129
+ assert b.grade == "chain" # now lintable ⇒ chain-graded
130
+ issues, _ = pc.lint_blocks(_wired(blocks=(b,)))
131
+ assert issues == [] # "A -> B -> C" contains "->" ⇒ clean
132
+ finally:
133
+ LANG_GATES.pop("arrowlang", None)
134
+
135
+
136
+ def test_seat_smoke_builds_a_gba(tmp_path: Path):
137
+ # seat() kwargs actually drive make_gba into a closed GBA (uses EINSTEIN — full cues).
138
+ from chainaios.gba import make_gba
139
+ bundle = _wired(blocks=(Block("role", "You are Einstein."),))
140
+ g = make_gba("physics", tmp_path / "aios", **pc.seat(bundle))
141
+ assert g.claude_md.exists()
142
+ assert g.closed