@agentikos/omega-os 0.19.37 → 0.19.39

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.
Files changed (39) hide show
  1. package/bin/omega-os.js +6 -1
  2. package/bootstrap/lib/steps.sh +43 -0
  3. package/install.sh +5 -0
  4. package/omega/Agentik_Engine/omega_engine/__init__.py +1 -1
  5. package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/omega/Agentik_Engine/omega_engine/__pycache__/cli.cpython-313.pyc +0 -0
  7. package/omega/Agentik_Engine/omega_engine/__pycache__/paperclip_bridge.cpython-313.pyc +0 -0
  8. package/omega/Agentik_Engine/omega_engine/__pycache__/prompt_audit.cpython-313.pyc +0 -0
  9. package/omega/Agentik_Engine/omega_engine/__pycache__/tmux.cpython-313.pyc +0 -0
  10. package/omega/Agentik_Engine/omega_engine/__pycache__/tui.cpython-313.pyc +0 -0
  11. package/omega/Agentik_Engine/omega_engine/cli.py +73 -0
  12. package/omega/Agentik_Engine/omega_engine/paperclip_bridge.py +110 -0
  13. package/omega/Agentik_Engine/omega_engine/prompt_audit.py +395 -0
  14. package/omega/Agentik_Engine/omega_engine/tmux.py +16 -0
  15. package/omega/Agentik_Engine/omega_engine/tui.py +269 -67
  16. package/omega/Agentik_Engine/pyproject.toml +1 -1
  17. package/omega/Agentik_Engine/tests/__pycache__/test_installer_wiring.cpython-313-pytest-8.4.2.pyc +0 -0
  18. package/omega/Agentik_Engine/tests/__pycache__/test_installer_wiring.cpython-313.pyc +0 -0
  19. package/omega/Agentik_Engine/tests/__pycache__/test_paperclip_status.cpython-313-pytest-8.4.2.pyc +0 -0
  20. package/omega/Agentik_Engine/tests/__pycache__/test_paperclip_status.cpython-313.pyc +0 -0
  21. package/omega/Agentik_Engine/tests/__pycache__/test_prompt_audit.cpython-313-pytest-8.4.2.pyc +0 -0
  22. package/omega/Agentik_Engine/tests/__pycache__/test_prompt_audit.cpython-313.pyc +0 -0
  23. package/omega/Agentik_Engine/tests/__pycache__/test_tui_runtime.cpython-313-pytest-8.4.2.pyc +0 -0
  24. package/omega/Agentik_Engine/tests/__pycache__/test_tui_runtime.cpython-313.pyc +0 -0
  25. package/omega/Agentik_Engine/tests/test_installer_wiring.py +130 -0
  26. package/omega/Agentik_Engine/tests/test_paperclip_status.py +142 -0
  27. package/omega/Agentik_Engine/tests/test_prompt_audit.py +199 -0
  28. package/omega/Agentik_Engine/tests/test_tui_runtime.py +106 -0
  29. package/omega/Agentik_SSOT/VERSION +1 -1
  30. package/omega/Agentik_SSOT/docs/AUDIT-V0.19.38.md +90 -0
  31. package/omega/Agentik_SSOT/docs/AUDIT-V0.19.39.md +161 -0
  32. package/omega/Agentik_SSOT/rules/audit-gates.md +189 -0
  33. package/omega/Agentik_SSOT/rules/constitution.md +7 -0
  34. package/omega/Agentik_SSOT/rules/orchestration.md +215 -0
  35. package/omega/Agentik_SSOT/rules/prompt-protocols.md +219 -0
  36. package/omega/Agentik_SSOT/rules/scope-safety.md +197 -0
  37. package/omega/Agentik_SSOT/rules/three-laws.md +214 -0
  38. package/omega/Agentik_SSOT/rules/verified-completion.md +216 -0
  39. package/package.json +1 -1
@@ -0,0 +1,395 @@
1
+ """Prompt audit — verify the AISB agent prompts are well-formed AND
2
+ reference the contracts they MUST reference.
3
+
4
+ Without this, the engine ships agents that read like generic system prompts
5
+ and the Three Laws / LMC protocol / verified-completion (`.done.json`)
6
+ contracts silently drift out of the role files. Orchestration breaks at
7
+ the first dispatch because a worker doesn't know its done-signal contract
8
+ or an oracle doesn't know it must enforce the laws.
9
+
10
+ The audit applies per-file (each role's prompt is scored in isolation —
11
+ not after the engine concatenates LMC + shared protocols at spawn time)
12
+ because the file on disk is what an operator reads and edits. If a role's
13
+ isolated file is silent on the Three Laws, the operator can't tell the
14
+ contract is present, and any edit that breaks the loader will silently
15
+ strip the laws too.
16
+
17
+ Public API:
18
+
19
+ - ``audit_agent_prompt(path)`` -> AgentPromptReport (score 0..100, checks dict)
20
+ - ``audit_aisb_suite(omega_home)`` -> SuiteReport (per-agent + averages)
21
+ - ``orchestration_health(omega_home)`` -> dict of presence + overlap
22
+
23
+ Only stdlib (``pathlib``, ``re``, ``dataclasses``). No new deps.
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import re
28
+ from dataclasses import dataclass, field
29
+ from pathlib import Path
30
+ from typing import Optional
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Data classes — what each audit returns
35
+ # ---------------------------------------------------------------------------
36
+
37
+
38
+ @dataclass
39
+ class CheckResult:
40
+ """One contract check on one prompt file."""
41
+
42
+ passed: bool
43
+ evidence: Optional[str] = None # short matched snippet, None if not found
44
+ points: int = 0 # points awarded for this check
45
+
46
+
47
+ @dataclass
48
+ class AgentPromptReport:
49
+ """Audit result for a single agent prompt .md file."""
50
+
51
+ agent_id: str
52
+ file_path: str
53
+ score: int = 0 # 0..100
54
+ checks: dict[str, CheckResult] = field(default_factory=dict)
55
+ violations: list[str] = field(default_factory=list) # human-readable
56
+
57
+
58
+ @dataclass
59
+ class SuiteReport:
60
+ """Audit result for the whole AISB suite."""
61
+
62
+ per_agent: list[AgentPromptReport] = field(default_factory=list)
63
+ average_score: float = 0.0
64
+ missing_critical: list[str] = field(default_factory=list) # score < 60
65
+ orchestration_chain_intact: bool = False
66
+
67
+
68
+ # ---------------------------------------------------------------------------
69
+ # Contract patterns — what every well-formed agent prompt should reference
70
+ # ---------------------------------------------------------------------------
71
+
72
+
73
+ # Three Laws — case-insensitive search for any of these markers
74
+ _RE_THREE_LAWS = re.compile(
75
+ r"(three\s+laws|first\s+law|second\s+law|third\s+law|"
76
+ r"law\s+1|law\s+2|law\s+3)",
77
+ re.IGNORECASE,
78
+ )
79
+
80
+ # LMC protocol — concept or filename
81
+ _RE_LMC = re.compile(
82
+ r"(lmc[\s\-_]*(protocol|gate)?|lead[\s\-]+manager[\s\-]+checker|"
83
+ r"lmc-protocol\.md)",
84
+ re.IGNORECASE,
85
+ )
86
+
87
+ # Verified-completion contract — `.done.json`
88
+ _RE_DONE_JSON = re.compile(r"\.done\.json|done\.json", re.IGNORECASE)
89
+
90
+ # Done-marker tooling — the script that writes the .done.json signal
91
+ _RE_DONE_MARKER = re.compile(
92
+ r"(worker[\s\-]?mark[\s\-]?done|oracle[\s\-]?mark[\s\-]?done|"
93
+ r"mark[\s\-]?done\.sh|write_done\b|done_signal|"
94
+ r"done[\s\-]?marker)",
95
+ re.IGNORECASE,
96
+ )
97
+
98
+ # Scope / role-specific responsibilities — at least one of these tokens
99
+ # tells us the file declares what the agent owns / is allowed to touch.
100
+ _RE_SCOPE = re.compile(
101
+ r"(scope|files?_owned|owns?\s+R[\s\-]?\d+|responsibilit(y|ies)|"
102
+ r"^\s*role\b|owned\s+files|files\s+owned|what\s+you\s+own|"
103
+ r"\bowns\b)",
104
+ re.IGNORECASE | re.MULTILINE,
105
+ )
106
+
107
+ # Fresh context handoff vocabulary
108
+ _RE_FRESH_CONTEXT = re.compile(
109
+ r"fresh[\s\-]+context|self[\s\-]+contained\s+brief|"
110
+ r"self[\s\-]+contained\s+context",
111
+ re.IGNORECASE,
112
+ )
113
+
114
+ # Banned phrases — the no-time-panic global rule (rule 46). These are the
115
+ # action-oriented forms that authorize cheating, NOT the descriptive forms
116
+ # (e.g. "lightweight utility agent" describes a haiku tier — fine).
117
+ # We explicitly DO NOT include bare "lightweight" because templates
118
+ # legitimately describe haiku-tier agents as lightweight (architecture
119
+ # descriptor, not a shortcut authorization).
120
+ _BANNED_PHRASES = [
121
+ "streamlined approach",
122
+ "streamlined version",
123
+ "skip audit",
124
+ "skip the audit",
125
+ "quick version",
126
+ "to save time",
127
+ "custom scoring",
128
+ "too heavyweight",
129
+ "lightweight audit",
130
+ "simplified protocol",
131
+ ]
132
+
133
+ # Per-check point weights (sum = 100)
134
+ _WEIGHTS = {
135
+ "three_laws": 25,
136
+ "lmc_protocol": 15,
137
+ "done_json": 20,
138
+ "done_marker": 10,
139
+ "scope": 15,
140
+ "fresh_context": 10,
141
+ "no_banned": 5,
142
+ }
143
+
144
+
145
+ # ---------------------------------------------------------------------------
146
+ # Helpers
147
+ # ---------------------------------------------------------------------------
148
+
149
+
150
+ def _short_evidence(text: str, match: re.Match) -> str:
151
+ """Return a short snippet (~60 chars) around the match for the report."""
152
+ start = max(0, match.start() - 10)
153
+ end = min(len(text), match.end() + 20)
154
+ snippet = text[start:end].replace("\n", " ").strip()
155
+ if len(snippet) > 80:
156
+ snippet = snippet[:77] + "..."
157
+ return snippet
158
+
159
+
160
+ def _check_pattern(
161
+ text: str,
162
+ pattern: re.Pattern,
163
+ name: str,
164
+ points: int,
165
+ ) -> CheckResult:
166
+ """Run a regex check and return a CheckResult."""
167
+ m = pattern.search(text)
168
+ if m:
169
+ return CheckResult(passed=True, evidence=_short_evidence(text, m),
170
+ points=points)
171
+ return CheckResult(passed=False, evidence=None, points=0)
172
+
173
+
174
+ def _check_no_banned(text: str, points: int) -> CheckResult:
175
+ """Pass iff no banned phrase appears. The phrase list is short and
176
+ targets action-oriented patterns (e.g. 'streamlined approach') rather
177
+ than descriptive ones (e.g. bare 'lightweight') to avoid false
178
+ positives in legitimate architecture descriptions."""
179
+ lowered = text.lower()
180
+ for phrase in _BANNED_PHRASES:
181
+ if phrase in lowered:
182
+ return CheckResult(passed=False, evidence=phrase, points=0)
183
+ return CheckResult(passed=True, evidence=None, points=points)
184
+
185
+
186
+ def _agent_id_from_path(path: Path) -> str:
187
+ """Derive a short agent id from the file path (the .stem)."""
188
+ return path.stem
189
+
190
+
191
+ # ---------------------------------------------------------------------------
192
+ # Public API — single file
193
+ # ---------------------------------------------------------------------------
194
+
195
+
196
+ def audit_agent_prompt(path: Path) -> AgentPromptReport:
197
+ """Score one agent prompt .md file on the seven contract checks.
198
+
199
+ Scoring (sum = 100):
200
+ - Three Laws reference 25 pts
201
+ - LMC protocol reference 15 pts
202
+ - `.done.json` reference 20 pts
203
+ - Done-marker tooling reference 10 pts
204
+ - Scope / files_owned / responsibilities 15 pts
205
+ - Fresh context / self-contained brief 10 pts
206
+ - No banned phrases (rule 46-no-time-panic) 5 pts
207
+
208
+ Returns a fully-populated :class:`AgentPromptReport`. If the file does
209
+ not exist or can't be read, returns a report with score=0 and a single
210
+ violation explaining why.
211
+ """
212
+ path = Path(path)
213
+ agent_id = _agent_id_from_path(path)
214
+ report = AgentPromptReport(agent_id=agent_id, file_path=str(path))
215
+
216
+ try:
217
+ text = path.read_text(encoding="utf-8")
218
+ except (OSError, UnicodeDecodeError) as exc:
219
+ report.violations.append(f"unreadable file: {exc}")
220
+ return report
221
+
222
+ checks: dict[str, CheckResult] = {
223
+ "three_laws": _check_pattern(text, _RE_THREE_LAWS,
224
+ "three_laws",
225
+ _WEIGHTS["three_laws"]),
226
+ "lmc_protocol": _check_pattern(text, _RE_LMC,
227
+ "lmc_protocol",
228
+ _WEIGHTS["lmc_protocol"]),
229
+ "done_json": _check_pattern(text, _RE_DONE_JSON,
230
+ "done_json",
231
+ _WEIGHTS["done_json"]),
232
+ "done_marker": _check_pattern(text, _RE_DONE_MARKER,
233
+ "done_marker",
234
+ _WEIGHTS["done_marker"]),
235
+ "scope": _check_pattern(text, _RE_SCOPE,
236
+ "scope",
237
+ _WEIGHTS["scope"]),
238
+ "fresh_context": _check_pattern(text, _RE_FRESH_CONTEXT,
239
+ "fresh_context",
240
+ _WEIGHTS["fresh_context"]),
241
+ "no_banned": _check_no_banned(text, _WEIGHTS["no_banned"]),
242
+ }
243
+ report.checks = checks
244
+
245
+ # Compute score and violations.
246
+ score = sum(c.points for c in checks.values())
247
+ report.score = min(100, score)
248
+
249
+ # Human-readable violations for the doctor line summary.
250
+ human = {
251
+ "three_laws": "Three Laws",
252
+ "lmc_protocol": "LMC protocol",
253
+ "done_json": "`.done.json` contract",
254
+ "done_marker": "done-marker tooling",
255
+ "scope": "scope/responsibilities",
256
+ "fresh_context": "fresh context handoff",
257
+ "no_banned": "banned phrase present",
258
+ }
259
+ for name, res in checks.items():
260
+ if not res.passed:
261
+ label = human[name]
262
+ if name == "no_banned" and res.evidence:
263
+ label = f"{label} ({res.evidence})"
264
+ report.violations.append(label)
265
+
266
+ return report
267
+
268
+
269
+ # ---------------------------------------------------------------------------
270
+ # Public API — full suite
271
+ # ---------------------------------------------------------------------------
272
+
273
+
274
+ def _aisb_dir(omega_home: Path) -> Path:
275
+ return Path(omega_home) / "Agentik_SSOT" / "agents" / "aisb"
276
+
277
+
278
+ def _iter_aisb_prompts(omega_home: Path) -> list[Path]:
279
+ """Return every .md directly under aisb/ (subdirs excluded)."""
280
+ aisb = _aisb_dir(omega_home)
281
+ if not aisb.is_dir():
282
+ return []
283
+ return sorted(p for p in aisb.iterdir()
284
+ if p.is_file() and p.suffix == ".md")
285
+
286
+
287
+ def audit_aisb_suite(omega_home: Path) -> SuiteReport:
288
+ """Audit every .md directly under ``aisb/`` (subdirs excluded).
289
+
290
+ Returns a :class:`SuiteReport`. If no prompts are found, ``per_agent``
291
+ is an empty list and downstream callers should warn (the install
292
+ step 25-aisb-suite probably was skipped).
293
+ """
294
+ omega_home = Path(omega_home)
295
+ paths = _iter_aisb_prompts(omega_home)
296
+ per_agent = [audit_agent_prompt(p) for p in paths]
297
+
298
+ if per_agent:
299
+ avg = sum(r.score for r in per_agent) / len(per_agent)
300
+ else:
301
+ avg = 0.0
302
+
303
+ missing_critical = sorted(r.agent_id for r in per_agent if r.score < 60)
304
+
305
+ return SuiteReport(
306
+ per_agent=per_agent,
307
+ average_score=avg,
308
+ missing_critical=missing_critical,
309
+ orchestration_chain_intact=_chain_intact(per_agent),
310
+ )
311
+
312
+
313
+ def _chain_intact(reports: list[AgentPromptReport]) -> bool:
314
+ """The AISB → Oracle → Worker chain is intact when:
315
+ - each of the three roles exists in the suite, AND
316
+ - all three reference `.done.json` (the shared completion vocabulary).
317
+
318
+ 'Worker' is taken loosely: morpheus (the canonical executor) or
319
+ construct (alias target for the generic 'worker' role) satisfies the
320
+ worker slot. This matches the alias map in ``prompts.py``.
321
+ """
322
+ by_id = {r.agent_id: r for r in reports}
323
+ aisb_master = by_id.get("CLAUDE") # the master prompt of the AISB suite
324
+ oracle = by_id.get("oracle")
325
+ worker = by_id.get("morpheus") or by_id.get("construct")
326
+
327
+ if not (aisb_master and oracle and worker):
328
+ return False
329
+ return all(r.checks.get("done_json", CheckResult(passed=False)).passed
330
+ for r in (aisb_master, oracle, worker))
331
+
332
+
333
+ # ---------------------------------------------------------------------------
334
+ # Public API — orchestration health
335
+ # ---------------------------------------------------------------------------
336
+
337
+
338
+ def orchestration_health(omega_home: Path) -> dict:
339
+ """Higher-level check: is the AISB → Oracle → Worker → Checker chain
340
+ wired and does the suite share the verified-completion vocabulary?
341
+
342
+ Returns a dict with:
343
+ - ``aisb_master_present`` — CLAUDE.md exists in aisb/
344
+ - ``oracle_present`` — oracle.md exists
345
+ - ``workers_role_present`` — morpheus.md OR construct.md exists
346
+ - ``checker_present`` — seraph.md OR smith.md exists
347
+ - ``lmc_protocol_present`` — lmc-protocol.md exists
348
+ - ``shared_vocab_overlap`` — fraction (0.0..1.0) of agents
349
+ mentioning `.done.json` in their file
350
+ """
351
+ omega_home = Path(omega_home)
352
+ aisb = _aisb_dir(omega_home)
353
+
354
+ def has(name: str) -> bool:
355
+ return (aisb / name).is_file()
356
+
357
+ out: dict = {
358
+ "aisb_master_present": has("CLAUDE.md"),
359
+ "oracle_present": has("oracle.md"),
360
+ "workers_role_present": has("morpheus.md") or has("construct.md"),
361
+ "checker_present": has("seraph.md") or has("smith.md"),
362
+ "lmc_protocol_present": has("lmc-protocol.md"),
363
+ }
364
+
365
+ # Shared vocabulary overlap — fraction of agents that reference
366
+ # `.done.json` in their file. A high overlap means the completion
367
+ # contract is part of the muscle memory of the suite; a low overlap
368
+ # means some agents are silent on the done signal and may complete
369
+ # tasks without writing the structured result downstream consumers
370
+ # expect.
371
+ paths = _iter_aisb_prompts(omega_home)
372
+ if not paths:
373
+ out["shared_vocab_overlap"] = 0.0
374
+ return out
375
+
376
+ matches = 0
377
+ for p in paths:
378
+ try:
379
+ text = p.read_text(encoding="utf-8")
380
+ except (OSError, UnicodeDecodeError):
381
+ continue
382
+ if _RE_DONE_JSON.search(text):
383
+ matches += 1
384
+ out["shared_vocab_overlap"] = matches / len(paths)
385
+ return out
386
+
387
+
388
+ __all__ = [
389
+ "CheckResult",
390
+ "AgentPromptReport",
391
+ "SuiteReport",
392
+ "audit_agent_prompt",
393
+ "audit_aisb_suite",
394
+ "orchestration_health",
395
+ ]
@@ -325,6 +325,22 @@ def _spawn_with_shell_then_run(
325
325
  return name
326
326
 
327
327
 
328
+ def omega_window_alive(window_name: str) -> bool:
329
+ """True if a window named ``window_name`` exists inside the Omega
330
+ master tmux session.
331
+
332
+ Used by the TUI chat-list panel to render ● (alive) vs ○ (off) next
333
+ to AISB-chat / Hermès-chat. Cheap — one ``tmux list-windows`` call;
334
+ returns False on any error including 'no Omega session'.
335
+ """
336
+ if not is_alive("Omega"):
337
+ return False
338
+ rc, out = _tmux("list-windows", "-t", "Omega", "-F", "#W")
339
+ if rc != 0:
340
+ return False
341
+ return window_name in (out or "").splitlines()
342
+
343
+
328
344
  def spawn_chat_in_omega(
329
345
  window_name: str,
330
346
  *,