chainaios 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,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: chainaios
3
+ Version: 0.3.0
4
+ Summary: The Bandit AIOS application on the ChainCompiler stack: mint a domain's AC+CoR+SC and roll them up into persistent BanditChain-system AIOS dirs (GBA/HBA/COG).
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: chaincompiler==0.3.0
7
+ Requires-Dist: chaincompiler-accc==0.3.0
8
+ Requires-Dist: corcc==0.3.0
9
+ Requires-Dist: sccc==0.3.0
10
+ Requires-Dist: agent-skilltree>=0.4.0
11
+ Requires-Dist: click
12
+ Requires-Dist: pyyaml
13
+ Provides-Extra: legend
14
+ Requires-Dist: glyphsteer==0.3.0; extra == "legend"
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "chainaios"
7
+ version = "0.3.0"
8
+ description = "The Bandit AIOS application on the ChainCompiler stack: mint a domain's AC+CoR+SC and roll them up into persistent BanditChain-system AIOS dirs (GBA/HBA/COG)."
9
+ requires-python = ">=3.11"
10
+ # The AIOS tier sits ABOVE the cognition-chain stack: it imports the *CC + the CC base. (CC + the *CC
11
+ # never import this — that is what keeps `chaincompiler` the compiler-compiler BASE, DESIGN.md §2.)
12
+ dependencies = ["chaincompiler==0.3.0", "chaincompiler-accc==0.3.0", "corcc==0.3.0", "sccc==0.3.0", "agent-skilltree>=0.4.0", "click", "pyyaml"]
13
+
14
+ # A lazy import is an optional extra (DESIGN.md §18): the roll-up writes each persona's GlyphSteer legend only when
15
+ # glyphsteer is present (bandit.py, `_write_persona_aios`).
16
+ [project.optional-dependencies]
17
+ legend = ["glyphsteer==0.3.0"]
18
+
19
+ # The `chaincompiler` CLI command now lives here (the AIOS commands are *CC-coupled); the command NAME is
20
+ # unchanged so existing `chaincompiler gba/cog/hba/demo …` invocations keep working.
21
+ [project.scripts]
22
+ chaincompiler = "chainaios.cli:main"
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["src"]
26
+
27
+ # the bandit-chain-system skill every GBA receives (DESIGN.md §8)
28
+ [tool.setuptools.package-data]
29
+ chainaios = ["data/bandit-chain-system/SKILL.md"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,30 @@
1
+ """chainaios — the Bandit AIOS application, ABOVE the cognition-chain stack.
2
+
3
+ This is the AIOS/orchestration tier that sits ON TOP of ACCC/CORCC/SCCC (it mints a domain's AC + CoR + SC
4
+ and rolls them up into persistent BanditChain-system AIOS dirs). It imports the *CC + the CC base; the CC
5
+ base and the *CC never import it. (DESIGN.md §2 keeps `chaincompiler` the compiler-compiler BASE — the
6
+ *CC are made FROM CC; this AIOS app is a separate top layer, extracted out of the CC package so CC no
7
+ longer imports the *CC.)
8
+
9
+ The `chaincompiler` console command lives here (entry `chainaios.cli:main`), so the CLI name is unchanged.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ __version__ = "0.3.0"
14
+
15
+ from .construct import construct_language, LanguageBundle
16
+ from .bandit import (
17
+ domain_bandit, BanditChainSystem, roll_up_algebra, SelfView, hierarchicalize, COMPONENTS,
18
+ )
19
+ from .gba import GBA, make_gba, construct_into, search, load_gba, check_gba, default_rule_blocks
20
+ from .hba import HBA, make_hba
21
+ from .cog import COG, make_cog, load_cog, PATTERNS, SEATS
22
+
23
+ __all__ = [
24
+ "construct_language", "LanguageBundle",
25
+ "domain_bandit", "BanditChainSystem", "roll_up_algebra", "SelfView", "hierarchicalize", "COMPONENTS",
26
+ "GBA", "make_gba", "construct_into", "search", "load_gba", "check_gba", "default_rule_blocks",
27
+ "HBA", "make_hba",
28
+ "COG", "make_cog", "load_cog", "PATTERNS", "SEATS",
29
+ "__version__",
30
+ ]
@@ -0,0 +1,312 @@
1
+ """bandit.py — the bandit's headline capability.
2
+
3
+ The default persona (`corcc.BANDIT`, the ChainSelector) does ONE big move: given a
4
+ domain, **roll up the AC→CoR→SC algebra and CLOSE it** into a self-contained
5
+ *bandit chain system* — a domain-specific persona (an AIOS dir) whose CoR is the
6
+ bandit, carrying instructions to (a) build its own KB and (b) walk that KB with
7
+ GlyphSteer chains to improve itself.
8
+
9
+ Then `hierarchicalize` applies the SAME move over everything the bandit is *made
10
+ of* (accc · corcc · sccc · glyphsteer · skilltree · si · honeyc ·
11
+ rulecatcher) — the homoicon: the bandit views its own composition as a domain and
12
+ mints a closed chain system for each part, organized into one granular self-view.
13
+
14
+ roll_up_algebra(domain, atoms, ...) -> BanditChainSystem (closed: bool)
15
+ hierarchicalize(workdir, ...) -> SelfView (a tree of systems)
16
+
17
+ Exported from `chainaios` (the agent tier above the chain compilers — DESIGN.md §7);
18
+ the chaincompiler base never imports it.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ from dataclasses import dataclass, field
23
+ from pathlib import Path
24
+
25
+ import accc
26
+ import corcc
27
+ import sccc
28
+ from corcc.notation import BANDIT, Move, PersonaSpec
29
+
30
+ from chaincompiler import slugify, write_skill # the CC base (chainaios is ABOVE CC + the *CC)
31
+
32
+
33
+ # ── the domain-specialized bandit (the selector, given a domain flavor) ──────
34
+ def domain_bandit(domain: str, blurb: str = "") -> PersonaSpec:
35
+ """The BANDIT (ChainSelector) specialized to a domain. Same explore/exploit
36
+ moves (Task→Recall→Decide→Execute→Reward); the domain rides in the name, the
37
+ blurb, and an added Task cue. A *flavor* is a single proven chain; this is the
38
+ selector itself, pointed at one domain — so it still chooses select-vs-construct."""
39
+ b = blurb or (f"in the {domain} domain: frame the task, recall a proven {domain} "
40
+ f"chain, choose exploit (select a golden chain) vs explore "
41
+ f"(construct a new one), execute it, record the reward")
42
+ moves = list(BANDIT.moves)
43
+ moves[0] = Move("Task", BANDIT.moves[0].cues + (f"the {domain} task", f"in {domain}"))
44
+ return PersonaSpec(name=f"{domain.capitalize()}Bandit", moves=tuple(moves), blurb=b)
45
+
46
+
47
+ # ── the two self-improvement instruction blocks the persona carries ──────────
48
+ def kb_instructions(domain: str) -> str:
49
+ """How this persona builds its OWN knowledge base (the first self-instruction)."""
50
+ return "\n".join([
51
+ f"## Build your own KB ({domain})",
52
+ "",
53
+ "You maintain a knowledge base under `kb/`. It is yours — grow it as you work:",
54
+ "",
55
+ f"1. **Record** — every time you run a {domain} chain, write what happened to a",
56
+ f" `kb/<topic>.md` note: the task, the chain you chose (select vs construct),",
57
+ " the reward, and what you'd do differently. One note per topic; append, don't overwrite.",
58
+ "2. **Tag** — head each note with a `glyphs:` line (a GlyphSteer legend facet, e.g.",
59
+ " `🏆`/`✅`/`⚠️`/`❌`) recording how well the chain worked. The grade is the reward signal.",
60
+ "3. **Index** — the KB is a corpus; treat each note as a chunk. The legend in",
61
+ " `legend.json` is the controlled glyph vocabulary you annotate with.",
62
+ ])
63
+
64
+
65
+ def glyphsteer_improve_instructions(domain: str) -> str:
66
+ """How this persona walks its KB with GlyphSteer chains to improve ITSELF."""
67
+ return "\n".join([
68
+ f"## Improve yourself via GlyphSteer chains ({domain})",
69
+ "",
70
+ "Your KB is not static — you read it back through GlyphSteer to get better:",
71
+ "",
72
+ "1. **Annotate** the KB with the legend (`glyphsteer.annotate_chunk`) so each note",
73
+ " carries its grade facet lexically (the ASCII tag) and densely (the emoji direction).",
74
+ "2. **Steer** retrieval: when a new task arrives, `glyphsteer.search(con, task, facet=🏆)`",
75
+ " surfaces your *best-graded* prior chains first — that is the `Recall` move made real.",
76
+ " The markers steer the match and are stripped before you read (the HIDE invariant).",
77
+ "3. **Promote** — when a constructed chain earns 🏆 twice, FILE A GOLDENIZATION REQUEST",
78
+ " (goldenreq) with the graded notes as evidence — never self-fold to golden; promotion",
79
+ " happens only on an accepted verdict. Demote ❌ chains out of `Recall`.",
80
+ "4. **Loop** — this is your reward update: the KB + the legend ARE your bandit policy,",
81
+ " improved by reading yourself. Re-run after every batch of tasks.",
82
+ ])
83
+
84
+
85
+ @dataclass
86
+ class BanditChainSystem:
87
+ """A rolled-up, CLOSED chain system for one domain — the bandit's output type.
88
+ Every field is (a path to) the one type, a skill dir, except `closed`/`report`."""
89
+ domain: str
90
+ ac: list[Path]
91
+ cor: Path
92
+ sc: Path
93
+ persona_dir: Path
94
+ tree_root: Path
95
+ closed: bool
96
+ report: dict = field(default_factory=dict)
97
+
98
+
99
+ def _write_persona_aios(domain: str, persona: PersonaSpec, ac: list[Path], cor: Path,
100
+ sc: Path, root: Path, *, selector: bool = True) -> Path:
101
+ """Write the domain-specific persona as an AIOS dir: CLAUDE.md (the persona) +
102
+ legend.json (the glyph vocab) + kb/ (its own knowledge base) + a skills/ pointer.
103
+ A persona IS a CLAUDE.md inside a dir — not a single SKILL.md.
104
+
105
+ `selector=True` = the default bandit (the ChainSelector); False = a custom
106
+ PersonaSpec CoR — same AIOS shape (KB + glyphsteer self-improvement), but the
107
+ role text and loop render from the persona's own moves."""
108
+ pdir = root / slugify(f"{domain}-bandit-persona" if selector
109
+ else f"{domain}-{persona.name}-persona")
110
+ pdir.mkdir(parents=True, exist_ok=True)
111
+ (pdir / "kb").mkdir(exist_ok=True)
112
+
113
+ # the GlyphSteer legend this persona annotates its KB with (built-in GRADE vocab)
114
+ try:
115
+ from glyphsteer import GRADE
116
+ from glyphsteer.legend import save_legend
117
+ save_legend(GRADE, pdir / "legend.json")
118
+ legend_line = "Legend: `legend.json` (GlyphSteer GRADE vocabulary: 🏆 ✅ ⚠️ ❌)."
119
+ except Exception: # glyphsteer not installed — the persona still stands
120
+ legend_line = "Legend: install `glyphsteer` to enable the grade vocabulary."
121
+
122
+ moves = " → ".join(m.name for m in persona.moves)
123
+ who = "the ChainSelector" if selector else "a chain-of-reasoning persona"
124
+ kind = "a bandit chain system" if selector else "a rolled-up chain system"
125
+ claude_md = "\n".join([
126
+ f"# {persona.name} — {kind} for `{domain}`",
127
+ "",
128
+ f"You are the **{persona.name}**: {who} ({moves}), specialized to the "
129
+ f"`{domain}` domain. {persona.blurb}.",
130
+ "",
131
+ "## Your closed algebra (the chains you were rolled up from)",
132
+ "",
133
+ "These three skill dirs are your minted, closed AC→CoR→SC algebra:",
134
+ "",
135
+ *[f"- **AC** (how to think): `{p.parent.name}`" for p in ac],
136
+ f"- **CoR** ({'the bandit, spoken' if selector else 'spoken — the melt-gauge'}): `{cor.parent.name}`",
137
+ f"- **SC** (the rollup): `{sc.parent.name}`",
138
+ "",
139
+ legend_line,
140
+ "",
141
+ kb_instructions(domain),
142
+ "",
143
+ glyphsteer_improve_instructions(domain),
144
+ "",
145
+ "## The loop",
146
+ "",
147
+ ("`Task → Recall (search your KB, 🏆-faceted) → Decide (select golden vs construct new) "
148
+ "→ Execute → Reward (grade it, write the KB note)` — then improve yourself by reading the KB back."
149
+ if selector else
150
+ f"`{moves}` — perform the moves in order on every task (the last move is your held "
151
+ "convergence; skipping or scrambling them = the persona is melting). Grade each run in "
152
+ "the KB, then improve yourself by reading the KB back."),
153
+ ])
154
+ (pdir / "CLAUDE.md").write_text(claude_md)
155
+ (pdir / "kb" / "README.md").write_text(
156
+ f"# {domain} KB\n\nThis persona's knowledge base. One note per topic, each headed "
157
+ f"with a `glyphs:` grade line. See `../CLAUDE.md` for the protocol.\n")
158
+ return pdir
159
+
160
+
161
+ def roll_up_algebra(domain: str, atoms: list[str] | dict[str, str | tuple[str, str]],
162
+ *, db: str, skills_dir: str,
163
+ out_dir: str, blurb: str = "", persona_root: str | None = None,
164
+ sequence: str | None = None,
165
+ persona: PersonaSpec | None = None) -> BanditChainSystem:
166
+ """Mint a domain's AC(s) + CoR + SC, build its persona AIOS, and CLOSE it.
167
+
168
+ `atoms` = the attention-chain strings (the inner templates): a list (auto-named
169
+ `{domain}-attention[-N]`) or a dict `name -> atom` / `name -> (atom, description)`
170
+ to control the minted AC package names. The CoR defaults to the domain-specialized
171
+ BANDIT (not a flavor — the selector itself); pass `persona=` a custom `PersonaSpec`
172
+ to seat a domain CoR instead (e.g. an SDNA v2 profile's melt-gauge chain) — same
173
+ AIOS + KB + glyphsteer self-improvement, role text rendered from its moves.
174
+ Closure = every minted artifact is the one type AND the organizing SkillTree
175
+ validates.
176
+ """
177
+ from skilltree import SkillTree, TreeNode, materialize, validate
178
+
179
+ skills_dir = str(skills_dir)
180
+ # 1. AC(s) — accc forges + packages each atom into a skill dir
181
+ if isinstance(atoms, dict):
182
+ normalized = [(name, *(val if isinstance(val, tuple) else (val, None)))
183
+ for name, val in atoms.items()]
184
+ else:
185
+ normalized = [((f"{domain}-attention" if len(atoms) == 1
186
+ else f"{domain}-attention-{i+1}"), atom, None)
187
+ for i, atom in enumerate(atoms)]
188
+ ac_paths: list[Path] = []
189
+ for name, atom, desc in normalized:
190
+ accc.forge(name, [atom], db=db)
191
+ ac_paths.append(Path(accc.package(name, atom, out_dir=skills_dir, description=desc)))
192
+
193
+ # 2. CoR — the domain-specialized bandit by default, or the supplied persona
194
+ selector = persona is None
195
+ persona = persona or domain_bandit(domain, blurb)
196
+ forged = corcc.forge_persona(persona, db=db)
197
+ cor = Path(corcc.package(forged, out_dir=skills_dir))
198
+
199
+ # 3. SC — roll the AC(s) + the bandit CoR up into one sequence (the closure op)
200
+ seq = sequence or (
201
+ " ⇒ ".join(f"[ac:{p.parent.name}]" for p in ac_paths)
202
+ + f" ⇒ [cor:{cor.parent.name}] ⇒ |{domain.capitalize()}|")
203
+ sccc.forge(domain, [seq], db=db)
204
+ sc = Path(sccc.package(domain, seq, out_dir=out_dir, skills_dir=skills_dir))
205
+
206
+ # 4. the domain-specific persona AIOS (CLAUDE.md + legend + KB + glyphsteer self-improve)
207
+ persona_dir = _write_persona_aios(
208
+ domain, persona, ac_paths, cor, sc, Path(persona_root or out_dir), selector=selector)
209
+
210
+ # 5. CLOSE — organize into a SkillTree and validate (the algebra closes ⇔ no violations)
211
+ tree = SkillTree(TreeNode(slugify(domain), "sc", description=f"{domain} — {persona.blurb}",
212
+ children=[TreeNode(slugify(persona.name), "cor", description=persona.blurb, children=[
213
+ TreeNode(slugify(p.parent.name), "ac", description=f"attend ({domain})") for p in ac_paths
214
+ ])]))
215
+ tree_root = Path(out_dir) / f"{slugify(domain)}-tree"
216
+ materialize(tree, tree_root)
217
+ violations = validate(tree_root)
218
+ minted_ok = all(p.exists() and p.name == "SKILL.md" for p in [*ac_paths, cor, sc])
219
+ closed = minted_ok and len(violations) == 0
220
+
221
+ return BanditChainSystem(
222
+ domain=domain, ac=ac_paths, cor=cor, sc=sc, persona_dir=persona_dir,
223
+ tree_root=tree_root, closed=closed,
224
+ report={"sequence": seq, "minted_ok": minted_ok, "violations": len(violations),
225
+ "moves": [m.name for m in persona.moves]})
226
+
227
+
228
+ # ── the bandit's own composition (what it is MADE OF) ───────────────────────
229
+ # Each entry: name -> (what that part IS, [its REAL operations as attention chains]).
230
+ # The atoms decompose each component into its actual public moves, so the self-view
231
+ # is genuinely granular: root → component → that component's operations.
232
+ COMPONENTS: dict[str, tuple[str, list[str]]] = {
233
+ "accc": ("attention chains — how to think; inner, silent templates", [
234
+ "[Examples] ⇒ [Grammar] ⇒ |Forge|",
235
+ "[AC] ⇒ [Grammar] ⇒ |Gate|",
236
+ "[Language] ⇒ [Foci] ⇒ |Package|"]),
237
+ "corcc": ("chains of reasoning — spoken, end in a decision", [
238
+ "[PersonaSpec] ⇒ [InnerAC] ⇒ |ForgePersona|",
239
+ "[CoRText] ⇒ [Moves] ⇒ |Lint|",
240
+ "[Persona] ⇒ [Chain] ⇒ |Package|"]),
241
+ "sccc": ("skill chains — sequence AC+CoR+skills into the closed composite", [
242
+ "[Sequence] ⇒ [StepRefs] ⇒ |Parse|",
243
+ "[Steps] ⇒ [Packages] ⇒ |Resolve|",
244
+ "[Sequence] ⇒ [Rollup] ⇒ |Package|"]),
245
+ "glyphsteer": ("dual-regime retrieval steering — glyph facets, hidden on return", [
246
+ "[Chunk] ⇒ [Glyph] ⇒ |Annotate|",
247
+ "[Chunks] ⇒ [FTS5] ⇒ |Index|",
248
+ "[Query] ⇒ [Facet] ⇒ |Search|",
249
+ "[Indexed] ⇒ [Clean] ⇒ |Hide|"]),
250
+ "skilltree": ("progressive-disclosure tree of skill dirs (Read-breadcrumbs)", [
251
+ "[Tree] ⇒ [Dirs] ⇒ |Materialize|",
252
+ "[Root] ⇒ [Breadcrumbs] ⇒ |Validate|",
253
+ "[Corpus] ⇒ [BM25] ⇒ |Search|"]),
254
+ "si": ("self-interpreter — walk + execute the tree", [
255
+ "[Root] ⇒ [Reachable] ⇒ |Walk|",
256
+ "[Name] ⇒ [SkillMd] ⇒ |Read|",
257
+ "[Source] ⇒ [Env] ⇒ |Interpret|"]),
258
+ "honeyc": ("the compiler — a dense-rune chain rendered many ways", [
259
+ "[Rune] ⇒ [AST] ⇒ |Parse|",
260
+ "[AST] ⇒ [Statements] ⇒ |Normalize|",
261
+ "[Statements] ⇒ [Lens] ⇒ |Render|"]),
262
+ "rulecatcher": ("the gate — learn a grammar, lint to ok/orthogonal/syntax_break", [
263
+ "[Examples] ⇒ [Rules] ⇒ |Learn|",
264
+ "[Text] ⇒ [Rules] ⇒ |Lint|",
265
+ "[Violations] ⇒ [Verdict] ⇒ |Classify|"]),
266
+ }
267
+
268
+
269
+ @dataclass
270
+ class SelfView:
271
+ """The bandit's granular view of itself: a closed chain system per component,
272
+ organized into one master SkillTree."""
273
+ systems: list[BanditChainSystem]
274
+ tree_root: Path
275
+ closed: bool
276
+ report: dict = field(default_factory=dict)
277
+
278
+
279
+ def hierarchicalize(*, workdir: str | Path, components: dict | None = None) -> SelfView:
280
+ """Apply `roll_up_algebra` over everything the bandit is composed of — a closed
281
+ chain system per part — and assemble them into ONE master SkillTree: the bandit's
282
+ more-granular view of itself (the homoicon, the self-application)."""
283
+ from skilltree import SkillTree, TreeNode, materialize, validate
284
+
285
+ components = components or COMPONENTS
286
+ work = Path(workdir); work.mkdir(parents=True, exist_ok=True)
287
+ db = str(work / "self.db")
288
+ skills = work / "skills"; skills.mkdir(exist_ok=True)
289
+ dist = work / "dist"; dist.mkdir(exist_ok=True)
290
+ personas = work / "personas"; personas.mkdir(exist_ok=True)
291
+
292
+ systems: list[BanditChainSystem] = []
293
+ for name, (blurb, atoms) in components.items():
294
+ systems.append(roll_up_algebra(
295
+ name, atoms, db=db, skills_dir=str(skills), out_dir=str(dist),
296
+ blurb=blurb, persona_root=str(personas)))
297
+
298
+ # the master self-view: root "bandit-self" over each component's rolled-up system
299
+ master = SkillTree(TreeNode("bandit-self", "sc",
300
+ description="the bandit's granular view of its own composition", children=[
301
+ TreeNode(slugify(s.domain), "cor", description=components[s.domain][0],
302
+ children=[TreeNode(slugify(p.parent.name), "ac",
303
+ description=f"attend ({s.domain})") for p in s.ac])
304
+ for s in systems]))
305
+ tree_root = work / "bandit-self-tree"
306
+ materialize(master, tree_root)
307
+ violations = validate(tree_root)
308
+ closed = all(s.closed for s in systems) and len(violations) == 0
309
+
310
+ return SelfView(systems=systems, tree_root=tree_root, closed=closed,
311
+ report={"components": list(components), "violations": len(violations),
312
+ "all_closed": all(s.closed for s in systems)})
@@ -0,0 +1,214 @@
1
+ """cc — ChainCompiler. Construct a whole cognition-language for a domain."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+ import tempfile
6
+
7
+ import click
8
+
9
+ from corcc.notation import Move, PersonaSpec
10
+
11
+ from .construct import construct_language
12
+
13
+ # A sample domain persona: triage an incident → decide severity.
14
+ TRIAGE = PersonaSpec(
15
+ name="TriageIncident",
16
+ blurb="scope the blast radius, find the trigger, decide severity",
17
+ moves=(
18
+ Move("BlastRadius", ("who is affected", "scope", "how many")),
19
+ Move("Trigger", ("what changed", "trigger", "first seen")),
20
+ Move("Severity", ("severity", "sev", "decide", "priority")),
21
+ ),
22
+ )
23
+
24
+
25
+ @click.group()
26
+ @click.version_option(message="cc %(version)s")
27
+ def main() -> None:
28
+ """ChainCompiler — the umbrella over ACCC / CORCC / SCCC."""
29
+
30
+
31
+ @main.command()
32
+ def layers() -> None:
33
+ """Print the stack."""
34
+ click.echo("CC (ChainCompiler)")
35
+ click.echo(" ACCC — attention template (how to think) → SKILL.md")
36
+ click.echo(" CORCC — spoken reasoning that makes a decision → SKILL.md (HAS AC)")
37
+ click.echo(" SCCC — chains AC + CoR + regular skills → SKILL.md (HAS CoR, AC, skills)")
38
+ click.echo(" each: catch grammar → LINT (syntax only) → compile .md → package <name>/SKILL.md")
39
+
40
+
41
+ @main.command()
42
+ def demo() -> None:
43
+ """Construct a brand-new domain language (AC + CoR + SC) in one call."""
44
+ with tempfile.TemporaryDirectory() as tmp:
45
+ bundle = construct_language(
46
+ "triage",
47
+ ac_chain="[Symptom] ⇒ [Scope] ⇒ [Change] ⇒ |Severity|",
48
+ cor_persona=TRIAGE,
49
+ db=str(Path(tmp) / "cc.db"),
50
+ skills_dir=str(Path(tmp) / "skills"),
51
+ out_dir=str(Path(tmp) / "dist"),
52
+ )
53
+ click.echo("══ CC.construct_language('triage') — minted a new cognition-language ══")
54
+ click.echo(f" AC → {bundle.ac.parent.name}/SKILL.md (how to think)")
55
+ click.echo(f" CoR → {bundle.cor.parent.name}/SKILL.md (spoken reasoning → decision)")
56
+ click.echo(f" SC → {bundle.sc.parent.name}/SKILL.md (chains AC + CoR)")
57
+ click.echo(f"\n default sequence: {bundle.sequence}")
58
+ click.echo("\n ── the SC rollup (publishable, auto-loadable by any agent) ──")
59
+ head = bundle.sc.read_text().splitlines()
60
+ click.echo("\n".join(" " + ln for ln in head[:14]))
61
+ click.echo("\n A new domain language, pre-powered with AC + CoR by default. "
62
+ "That is the bandit's 'construct-chain-language' move.")
63
+
64
+
65
+ @main.command()
66
+ @click.argument("prompt_file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
67
+ @click.option("--out", "out_dir", default="dist", type=click.Path(path_type=Path),
68
+ help="output dir for the compiled <cogid>/ skill")
69
+ def persona(prompt_file: Path, out_dir: Path) -> None:
70
+ """Compile a glyph-persona-program (e.g. BizziBee) → <cogid>/SKILL.md + legend.json.
71
+
72
+ Parses the three layers — [VarDefs] legend, ⚙️ workflow, [ROLE] wrapper — into the
73
+ artifacts the rest of ChainCompiler eats. Gates a legend chain via rulecatcher.
74
+ """
75
+ from chaincompiler.persona import compile_persona
76
+ res = compile_persona(prompt_file.read_text(encoding="utf-8"), out_dir)
77
+ click.echo(f"══ compiled persona '{res['name']}' ══")
78
+ click.echo(f" legend : {res['axes']} glyph axes → {res['legend']}")
79
+ click.echo(f" workflow: {res['steps']} ⚙️ steps")
80
+ click.echo(f" skill : {res['skill']}")
81
+ if res["gate"]:
82
+ click.echo(f" gate : chain {res['gate']['chain']} → {res['gate']['verdict']} (rulecatcher)")
83
+
84
+
85
+ @main.group()
86
+ def gba() -> None:
87
+ """General BanditAgent — a persistent AIOS dir an agent goes to and BECOMES."""
88
+
89
+
90
+ @gba.command("new")
91
+ @click.argument("domain")
92
+ @click.argument("root", type=click.Path(path_type=Path))
93
+ @click.option("--atom", "-a", "atoms", multiple=True, required=True,
94
+ help="an attention chain, e.g. -a '[Symptom] ⇒ [Scope] ⇒ |Severity|'")
95
+ def gba_new(domain: str, root: Path, atoms: tuple[str, ...]) -> None:
96
+ """Emit a persistent GBA AIOS at ROOT (CLAUDE.md + live tree + index + kb)."""
97
+ from .gba import make_gba
98
+ g = make_gba(domain, root, atoms=list(atoms))
99
+ click.echo(f"══ GBA '{domain}' → {g.root} ══")
100
+ click.echo(f" closed={g.closed} · {g.report['indexed_skills']} skills indexed · persona: CLAUDE.md")
101
+ click.echo(f" become it: cd {g.root} and run an agent there.")
102
+
103
+
104
+ @gba.command("construct")
105
+ @click.argument("root", type=click.Path(exists=True, path_type=Path))
106
+ @click.argument("name")
107
+ @click.argument("atoms", nargs=-1, required=True)
108
+ @click.option("--persona-json", type=click.Path(exists=True, path_type=Path), default=None,
109
+ help="JSON {name, blurb, moves:[{name, cues:[...]}]} — seat a bespoke CoR "
110
+ "(the melt-gauge) instead of the default bandit. This is how a walked "
111
+ "SDNA_Synth places a character with its OWN persona, not the selector.")
112
+ def gba_construct(root: Path, name: str, atoms: tuple[str, ...],
113
+ persona_json: Path | None) -> None:
114
+ """Mint a new chain system NAME into the GBA at ROOT (persists into the tree + re-indexes).
115
+
116
+ With --persona-json, the placed character gets a bespoke chain-of-reasoning seat (its own
117
+ melt-gauge) rather than the generic ChainSelector — the operational half of SDNA_Synth."""
118
+ import json as _json
119
+ from .gba import load_gba, construct_into
120
+ from corcc.notation import Move, PersonaSpec
121
+ persona = None
122
+ if persona_json is not None:
123
+ spec = _json.loads(Path(persona_json).read_text())
124
+ persona = PersonaSpec(name=spec["name"], blurb=spec.get("blurb", ""),
125
+ moves=tuple(Move(m["name"], tuple(m.get("cues", [])))
126
+ for m in spec["moves"]))
127
+ g = load_gba(root)
128
+ sysd = construct_into(g, name, list(atoms), persona=persona)
129
+ seat = persona.name if persona else "bandit (default selector)"
130
+ click.echo(f"constructed '{name}' → {sysd.sc.parent.name} (CoR seat: {seat}; "
131
+ f"{g.report['indexed_skills']} skills now)")
132
+
133
+
134
+ @gba.command("search")
135
+ @click.argument("root", type=click.Path(exists=True, path_type=Path))
136
+ @click.argument("query")
137
+ @click.option("--scope", "scope_coord", default=None,
138
+ help="restrict to a coordinate subtree, e.g. --scope 0.1 (where you are = what you see)")
139
+ @click.option("--newest", "newest_only", is_flag=True, default=False,
140
+ help="forward only the newest version per logical skill (the self-expansion routing)")
141
+ def gba_search(root: Path, query: str, scope_coord: str | None, newest_only: bool) -> None:
142
+ """Select's search half: BM25 over the GBA's live tree, optionally scoped + newest-version-routed."""
143
+ from .gba import load_gba, search
144
+ g = load_gba(root)
145
+ hits = search(g, query, scope_coord=scope_coord, newest_only=newest_only)
146
+ if not hits:
147
+ click.echo(" (no hits)")
148
+ for h in hits:
149
+ click.echo(f" [{h.get('coord','') or '–'}] {h['name']:28s} — {h.get('description','')}")
150
+
151
+
152
+ @gba.command("check")
153
+ @click.argument("root", type=click.Path(exists=True, path_type=Path))
154
+ def gba_check(root: Path) -> None:
155
+ """Re-check the GBA at ROOT is closed: its bodies present, its tree the manifest's and valid, no skill outside the
156
+ tree, its frontmatter parsing (exit 1 if not)."""
157
+ from .gba import check_gba
158
+ r = check_gba(root)
159
+ click.echo(f"closed={r['closed']} · {r['skills']} skills · {len(r['violations'])} violations · "
160
+ f"{len(r['missing_sources'])} missing sources · tree matches manifest={r['tree_matches_manifest']} · "
161
+ f"{len(r['unlisted_skills'])} unlisted skills · {len(r['unparsed_frontmatter'])} unparsed frontmatter")
162
+ for kind in ("violations", "missing_sources", "unlisted_skills", "unparsed_frontmatter"):
163
+ for item in r[kind]:
164
+ click.echo(f" {kind}: {item}")
165
+ raise SystemExit(0 if r["closed"] else 1)
166
+
167
+
168
+ @gba.command("hba")
169
+ @click.argument("domain")
170
+ @click.argument("root", type=click.Path(path_type=Path))
171
+ @click.option("--atom", "-a", "atoms", multiple=True, required=True,
172
+ help="an attention chain for the HBA's tree")
173
+ def gba_hba(domain: str, root: Path, atoms: tuple[str, ...]) -> None:
174
+ """Emit an HBA AIOS at ROOT: a GBA + select/construct seat subagents + the dispatch protocol."""
175
+ from .hba import make_hba
176
+ h = make_hba(domain, root, atoms=list(atoms))
177
+ click.echo(f"══ HBA '{domain}' → {h.root} ══")
178
+ click.echo(f" seats (subagents): {', '.join(p.name for p in sorted(h.agents_dir.glob('*.md')))}")
179
+ click.echo(f" dispatch protocol: .claude/rules/01-hba-dispatch.md · base GBA tree + index")
180
+
181
+
182
+ @main.group()
183
+ def cog() -> None:
184
+ """COG — a Challenger·Observer·Generator whose three seats are each a GBA."""
185
+
186
+
187
+ @cog.command("new")
188
+ @click.argument("domain")
189
+ @click.argument("root", type=click.Path(path_type=Path))
190
+ @click.option("--atom", "-a", "atoms", multiple=True, required=True,
191
+ help="an attention chain for the Generator seat")
192
+ def cog_new(domain: str, root: Path, atoms: tuple[str, ...]) -> None:
193
+ """Emit a COG AIOS at ROOT: the C→G→O flow skill + three role-AIOS seats (C/O/G) + a workspace."""
194
+ from .cog import make_cog
195
+ c = make_cog(domain, root, atoms=list(atoms))
196
+ click.echo(f"══ COG '{domain}' → {c.root} (flow valid: {c.closed}) ══")
197
+ click.echo(f" seats: ./C ./G ./O (each an AIOS you go to) · shared: ./workspace")
198
+ click.echo(f" flow : cat {c.flow.relative_to(c.root) if c.flow else '—'} (the C→G→O workflow you walk)")
199
+
200
+
201
+ @cog.command("flow")
202
+ @click.argument("root", type=click.Path(exists=True, path_type=Path))
203
+ def cog_flow(root: Path) -> None:
204
+ """Print the COG's default-workflow flow skill — the C→G→O flow you walk (it's a prompt, not a run)."""
205
+ from .cog import load_cog
206
+ c = load_cog(root)
207
+ if c.flow and c.flow.exists():
208
+ click.echo(c.flow.read_text())
209
+ else:
210
+ click.echo("(no flow skill found — re-run `cog new`)")
211
+
212
+
213
+ if __name__ == "__main__":
214
+ main()