arkaos 4.14.2 → 4.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.14.2
1
+ 4.14.3
@@ -0,0 +1,36 @@
1
+ {
2
+ "_meta": {
3
+ "generator": "core/agents/roster_manifest.py",
4
+ "purpose": "every specialist-gate owner resolves to a dispatchable agent source; installer/skill-deploy.js deploys these"
5
+ },
6
+ "gate_owners": {
7
+ "architect": {
8
+ "compiled": false,
9
+ "source": "departments/dev/agents/architect.md"
10
+ },
11
+ "backend-dev": {
12
+ "compiled": true,
13
+ "source": "config/claude-agents/backend-dev.md"
14
+ },
15
+ "dba": {
16
+ "compiled": true,
17
+ "source": "config/claude-agents/dba.md"
18
+ },
19
+ "devops-eng": {
20
+ "compiled": true,
21
+ "source": "config/claude-agents/devops-eng.md"
22
+ },
23
+ "frontend-dev": {
24
+ "compiled": false,
25
+ "source": "departments/dev/agents/frontend-dev.md"
26
+ },
27
+ "security-eng": {
28
+ "compiled": true,
29
+ "source": "config/claude-agents/security-eng.md"
30
+ },
31
+ "senior-dev": {
32
+ "compiled": false,
33
+ "source": "departments/dev/agents/senior-dev.md"
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,114 @@
1
+ """Agent roster manifest — every gate owner must be dispatchable.
2
+
3
+ Root cause this kills (operator incident, 2026-07-12): the specialist
4
+ gate blocked a lead from writing ``app/Http/Controllers/`` and told it
5
+ to dispatch ``senior-dev`` or ``backend-dev`` — but 4 of the 7 owners
6
+ named in ``config/agent-ownership.yaml`` (backend-dev, dba, devops-eng,
7
+ security-eng) were NOT dispatchable on real machines: they exist only
8
+ as department YAML, and the installer's agent deploy only ships ``.md``
9
+ files. A gate that demands an owner nobody can invoke is a dead end,
10
+ and dead ends teach sessions to rationalize bypasses.
11
+
12
+ This module is the single writer of ``config/agent-roster.json``:
13
+
14
+ python -m core.agents.roster_manifest
15
+
16
+ For every owner the ownership rules name, it resolves a dispatchable
17
+ source — a hand-authored ``departments/*/agents/<slug>.md`` (legacy,
18
+ wins because it may be hand-tuned) or the compiled
19
+ ``config/claude-agents/<slug>.md`` (generated from the agent YAML by
20
+ the behavioral compiler) — and REFUSES to emit when neither exists:
21
+ a missing source is a build-time failure, never a runtime dead end.
22
+ ``installer/skill-deploy.js`` reads the manifest and deploys every
23
+ gate owner; ``tests/python/test_agent_roster.py`` drift-gates it.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import json
29
+ import re
30
+ from pathlib import Path
31
+
32
+ import yaml
33
+
34
+ REPO_ROOT = Path(__file__).resolve().parents[2]
35
+ OWNERSHIP_YAML = REPO_ROOT / "config" / "agent-ownership.yaml"
36
+ COMPILED_DIR = REPO_ROOT / "config" / "claude-agents"
37
+ DEPARTMENTS_DIR = REPO_ROOT / "departments"
38
+ ROSTER_JSON = REPO_ROOT / "config" / "agent-roster.json"
39
+
40
+ _NAME_RE = re.compile(r"^name:\s*(\S+)\s*$", re.MULTILINE)
41
+
42
+
43
+ def gate_owners() -> list[str]:
44
+ """Every owner slug the ownership rules can demand (never ``*``)."""
45
+ config = yaml.safe_load(OWNERSHIP_YAML.read_text(encoding="utf-8"))
46
+ return sorted({
47
+ owner
48
+ for rule in config["ownership"]
49
+ for owner in (rule.get("owners") or [])
50
+ if owner != "*"
51
+ })
52
+
53
+
54
+ def _frontmatter_name(md_path: Path) -> str | None:
55
+ match = _NAME_RE.search(md_path.read_text(encoding="utf-8")[:2000])
56
+ return match.group(1) if match else None
57
+
58
+
59
+ def _resolve_source(slug: str) -> Path | None:
60
+ """Dispatchable source for a slug: hand-authored dept .md wins,
61
+ compiled claude-agents .md is the generated fallback."""
62
+ for candidate in sorted(DEPARTMENTS_DIR.glob(f"*/agents/{slug}.md")):
63
+ if _frontmatter_name(candidate) == slug:
64
+ return candidate
65
+ compiled = COMPILED_DIR / f"{slug}.md"
66
+ if compiled.is_file() and _frontmatter_name(compiled) == slug:
67
+ return compiled
68
+ return None
69
+
70
+
71
+ def build_roster() -> dict:
72
+ owners = gate_owners()
73
+ entries: dict[str, dict] = {}
74
+ ghosts: list[str] = []
75
+ for slug in owners:
76
+ source = _resolve_source(slug)
77
+ if source is None:
78
+ ghosts.append(slug)
79
+ continue
80
+ entries[slug] = {
81
+ "source": str(source.relative_to(REPO_ROOT)),
82
+ "compiled": source.parent == COMPILED_DIR,
83
+ }
84
+ if ghosts:
85
+ raise ValueError(
86
+ f"ownership rules demand owners with NO dispatchable source: "
87
+ f"{ghosts} — author the agent (YAML + compiler run) or fix "
88
+ f"config/agent-ownership.yaml; a gate must never demand an "
89
+ f"owner nobody can invoke"
90
+ )
91
+ return {
92
+ "_meta": {
93
+ "generator": "core/agents/roster_manifest.py",
94
+ "purpose": (
95
+ "every specialist-gate owner resolves to a dispatchable "
96
+ "agent source; installer/skill-deploy.js deploys these"
97
+ ),
98
+ },
99
+ "gate_owners": entries,
100
+ }
101
+
102
+
103
+ def render() -> str:
104
+ return json.dumps(build_roster(), indent=2, sort_keys=True) + "\n"
105
+
106
+
107
+ def main() -> int:
108
+ ROSTER_JSON.write_text(render(), encoding="utf-8")
109
+ print(f"agent roster written: {ROSTER_JSON}")
110
+ return 0
111
+
112
+
113
+ if __name__ == "__main__":
114
+ raise SystemExit(main())
@@ -135,13 +135,47 @@ export function deploySkills({
135
135
 
136
136
  // ── Agent personas ──────────────────────────────────────────────────
137
137
  if (agentsBase) {
138
- counts.agents = deployAgents(deptRoot, agentsBase);
138
+ const written = new Set();
139
+ counts.agents = deployAgents(deptRoot, agentsBase, written);
140
+ // Gate owners (F2 specialist-gate P0): every owner the ownership
141
+ // rules can demand MUST be dispatchable. 4 of 7 owners existed only
142
+ // as department YAML (never deployed — the agent loop above ships
143
+ // .md only), so the gate told sessions to dispatch agents nobody
144
+ // could invoke. The generated config/agent-roster.json maps each
145
+ // owner to a dispatchable source; hand-authored dept .md files win
146
+ // (already deployed above), compiled ones fill the gaps.
147
+ counts.agents += deployGateOwners(repoRoot, agentsBase, written);
139
148
  }
140
149
 
141
150
  log(counts);
142
151
  return counts;
143
152
  }
144
153
 
154
+ function deployGateOwners(repoRoot, agentsBase, written) {
155
+ let roster;
156
+ try {
157
+ roster = JSON.parse(readFileSync(
158
+ join(repoRoot, "config", "agent-roster.json"), "utf-8"));
159
+ } catch {
160
+ return 0; // older core without the roster — dept loop already ran
161
+ }
162
+ let deployed = 0;
163
+ for (const [slug, entry] of Object.entries(roster.gate_owners || {})) {
164
+ const target = `arka-${slug}.md`;
165
+ if (written.has(target)) continue; // dept .md won this run
166
+ const src = join(repoRoot, entry.source);
167
+ if (!existsSync(src)) continue;
168
+ try {
169
+ copyFileSync(src, join(agentsBase, target));
170
+ written.add(target);
171
+ deployed++;
172
+ } catch {
173
+ // One unreadable agent must not break the deploy.
174
+ }
175
+ }
176
+ return deployed;
177
+ }
178
+
145
179
  function loadCuratedFilter(repoRoot) {
146
180
  try {
147
181
  const manifest = JSON.parse(readFileSync(
@@ -156,14 +190,14 @@ function loadCuratedFilter(repoRoot) {
156
190
  }
157
191
  }
158
192
 
159
- function deployAgents(deptRoot, agentsBase) {
193
+ function deployAgents(deptRoot, agentsBase, written = new Set()) {
160
194
  mkdirSync(agentsBase, { recursive: true });
161
195
  let deployed = 0;
162
196
  for (const dept of listSubdirs(deptRoot)) {
163
197
  const agentsSrc = join(deptRoot, dept, "agents");
164
198
  if (!existsSync(agentsSrc)) continue;
165
199
  try {
166
- deployed += deployDeptAgents(agentsSrc, agentsBase);
200
+ deployed += deployDeptAgents(agentsSrc, agentsBase, written);
167
201
  } catch {
168
202
  // Unreadable agents dir — skip the department, not the deploy.
169
203
  }
@@ -171,7 +205,7 @@ function deployAgents(deptRoot, agentsBase) {
171
205
  return deployed;
172
206
  }
173
207
 
174
- function deployDeptAgents(agentsSrc, agentsBase) {
208
+ function deployDeptAgents(agentsSrc, agentsBase, written) {
175
209
  let deployed = 0;
176
210
  for (const file of readdirSync(agentsSrc)) {
177
211
  if (!file.endsWith(".md")) continue;
@@ -181,8 +215,9 @@ function deployDeptAgents(agentsSrc, agentsBase) {
181
215
  } catch {
182
216
  continue;
183
217
  }
184
- copyFileSync(
185
- srcFile, join(agentsBase, `arka-${file.replace(/\.md$/, "")}.md`));
218
+ const target = `arka-${file.replace(/\.md$/, "")}.md`;
219
+ copyFileSync(srcFile, join(agentsBase, target));
220
+ written.add(target);
186
221
  deployed++;
187
222
  }
188
223
  return deployed;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_meta": {
3
3
  "generator": "scripts/marketplace_gen.py",
4
- "version": "4.14.2",
4
+ "version": "4.14.3",
5
5
  "marketplace": "arkaos"
6
6
  },
7
7
  "structural": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "4.14.2",
3
+ "version": "4.14.3",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "4.14.2"
3
+ version = "4.14.3"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}