@agentikos/omega-os 0.1.0
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/LICENSE +21 -0
- package/README.md +127 -0
- package/bin/omega-os.js +48 -0
- package/bootstrap/lib/common.sh +73 -0
- package/bootstrap/lib/steps.sh +153 -0
- package/bootstrap/manifest.example.yaml +45 -0
- package/docs/ACCOUNT-AND-BILLING.md +95 -0
- package/docs/ARCHITECTURE.md +225 -0
- package/docs/AUTONOMOUS-AGENTS.md +128 -0
- package/docs/ENGINE-SPEC.md +174 -0
- package/docs/INSTALL.md +106 -0
- package/docs/MCP-AND-PLUGINS.md +121 -0
- package/docs/RUNTIME-PLAN.md +63 -0
- package/install.sh +54 -0
- package/omega/Agentik_Coding/README.md +21 -0
- package/omega/Agentik_Engine/README.md +58 -0
- package/omega/Agentik_Engine/omega_engine/__init__.py +58 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/audit.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/audit_arsenal.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/barrier.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/bus.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/cli.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/events.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/executor.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/mission.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/progress.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/project.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/provider.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/reducer.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/report.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/router.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/store.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/supervisor.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/task.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/__pycache__/telegram.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/omega_engine/audit.py +96 -0
- package/omega/Agentik_Engine/omega_engine/audit_arsenal.py +314 -0
- package/omega/Agentik_Engine/omega_engine/barrier.py +45 -0
- package/omega/Agentik_Engine/omega_engine/bus.py +45 -0
- package/omega/Agentik_Engine/omega_engine/cli.py +158 -0
- package/omega/Agentik_Engine/omega_engine/events.py +60 -0
- package/omega/Agentik_Engine/omega_engine/executor.py +167 -0
- package/omega/Agentik_Engine/omega_engine/mission.py +145 -0
- package/omega/Agentik_Engine/omega_engine/progress.py +75 -0
- package/omega/Agentik_Engine/omega_engine/project.py +92 -0
- package/omega/Agentik_Engine/omega_engine/provider.py +139 -0
- package/omega/Agentik_Engine/omega_engine/reducer.py +76 -0
- package/omega/Agentik_Engine/omega_engine/report.py +146 -0
- package/omega/Agentik_Engine/omega_engine/router.py +34 -0
- package/omega/Agentik_Engine/omega_engine/store.py +97 -0
- package/omega/Agentik_Engine/omega_engine/supervisor.py +69 -0
- package/omega/Agentik_Engine/omega_engine/task.py +91 -0
- package/omega/Agentik_Engine/omega_engine/telegram.py +115 -0
- package/omega/Agentik_Engine/pyproject.toml +31 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_audit_arsenal.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_executor.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_mission.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_progress.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_project.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_reducer.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/__pycache__/test_report.cpython-313.pyc +0 -0
- package/omega/Agentik_Engine/tests/test_audit_arsenal.py +80 -0
- package/omega/Agentik_Engine/tests/test_executor.py +96 -0
- package/omega/Agentik_Engine/tests/test_mission.py +64 -0
- package/omega/Agentik_Engine/tests/test_progress.py +69 -0
- package/omega/Agentik_Engine/tests/test_project.py +61 -0
- package/omega/Agentik_Engine/tests/test_reducer.py +144 -0
- package/omega/Agentik_Engine/tests/test_report.py +88 -0
- package/omega/Agentik_Extra/README.md +37 -0
- package/omega/Agentik_Extra/etc/agentik.env.example +19 -0
- package/omega/Agentik_Extra/etc/structure.yaml +46 -0
- package/omega/Agentik_Orchestration/README.md +43 -0
- package/omega/Agentik_Orchestration/autonomous/README.md +29 -0
- package/omega/Agentik_Orchestration/autonomous/example-agents.yaml +85 -0
- package/omega/Agentik_Orchestration/educators/README.md +55 -0
- package/omega/Agentik_Orchestration/topologies/aisb-oracle-worker.yaml +42 -0
- package/omega/Agentik_Orchestration/verifier/audit-router.yaml +26 -0
- package/omega/Agentik_Providers/README.md +62 -0
- package/omega/Agentik_Providers/claude/accounts.example.yaml +28 -0
- package/omega/Agentik_Providers/registry.yaml +30 -0
- package/omega/Agentik_Runtime/README.md +30 -0
- package/omega/Agentik_SSOT/README.md +36 -0
- package/omega/Agentik_SSOT/VERSION +1 -0
- package/omega/Agentik_SSOT/audits/a11yaudit.yaml +69 -0
- package/omega/Agentik_SSOT/audits/apiaudit.yaml +71 -0
- package/omega/Agentik_SSOT/audits/automationaudit.yaml +77 -0
- package/omega/Agentik_SSOT/audits/codeaudit.yaml +63 -0
- package/omega/Agentik_SSOT/audits/copyaudit.yaml +68 -0
- package/omega/Agentik_SSOT/audits/dataaudit.yaml +76 -0
- package/omega/Agentik_SSOT/audits/debugaudit.yaml +75 -0
- package/omega/Agentik_SSOT/audits/dxaudit.yaml +78 -0
- package/omega/Agentik_SSOT/audits/featureaudit.yaml +73 -0
- package/omega/Agentik_SSOT/audits/flowaudit.yaml +72 -0
- package/omega/Agentik_SSOT/audits/logicaudit.yaml +75 -0
- package/omega/Agentik_SSOT/audits/motionaudit.yaml +67 -0
- package/omega/Agentik_SSOT/audits/perfaudit.yaml +71 -0
- package/omega/Agentik_SSOT/audits/refontaudit.yaml +77 -0
- package/omega/Agentik_SSOT/audits/retentionaudit.yaml +84 -0
- package/omega/Agentik_SSOT/audits/secaudit.yaml +73 -0
- package/omega/Agentik_SSOT/audits/seoaudit.yaml +75 -0
- package/omega/Agentik_SSOT/audits/uiuxaudit.yaml +61 -0
- package/omega/Agentik_SSOT/mcp/mcp-catalog.yaml +136 -0
- package/omega/Agentik_SSOT/rules/constitution.md +44 -0
- package/omega/Agentik_SSOT/schemas/event.schema.json +45 -0
- package/omega/Agentik_SSOT/schemas/task.schema.json +54 -0
- package/omega/Agentik_Tools/README.md +42 -0
- package/omega/Agentik_Tools/registry.json +15 -0
- package/package.json +43 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""The Quality Arsenal — forensic audits as the OmegaOS verification layer.
|
|
2
|
+
|
|
3
|
+
The 18 forensic audits (code, security, performance, ...) become structured,
|
|
4
|
+
agentic audit modules. Each is a compact definition in `Agentik_SSOT/audits/`;
|
|
5
|
+
the common Gestalt-Popper shell — hinge point, adversarial framing, Popper
|
|
6
|
+
falsification, scoring, the fix loop — lives here, once.
|
|
7
|
+
|
|
8
|
+
A forensic audit runs in two passes:
|
|
9
|
+
1. GATHER — deterministic tools (ruff, eslint, ...) collect machine-checkable
|
|
10
|
+
findings. No LLM, no opinion.
|
|
11
|
+
2. FALSIFY — an agentic pass: a provider call that, given the gather findings
|
|
12
|
+
and the artifacts, runs the domain phases under the Popper
|
|
13
|
+
doctrine ("prove it lies") and emits a STRUCTURED verdict +
|
|
14
|
+
fix plan.
|
|
15
|
+
|
|
16
|
+
`ArsenalGate` plugs into the executor exactly where the simple `AuditGate` did —
|
|
17
|
+
it IS the `CLAIMED_DONE → VERIFIED` transition, now backed by the real arsenal.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import fnmatch
|
|
22
|
+
import json
|
|
23
|
+
import subprocess
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from omega_engine.audit import AuditFinding, AuditVerdict, audit_runtime_flow
|
|
29
|
+
from omega_engine.provider import AgentProvider, AgentRequest
|
|
30
|
+
|
|
31
|
+
# --------------------------------------------------------------------------
|
|
32
|
+
# Structured verdict types — "structured facts + fix plan", machine-actionable.
|
|
33
|
+
# --------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ArsenalFinding:
|
|
38
|
+
audit: str
|
|
39
|
+
phase: str
|
|
40
|
+
severity: str # critical | high | medium | low
|
|
41
|
+
location: str
|
|
42
|
+
claim: str # what the code/feature claims
|
|
43
|
+
reality: str # what it actually does
|
|
44
|
+
category: str = "claim-vs-reality"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class FixTask:
|
|
49
|
+
id: str
|
|
50
|
+
finding: str
|
|
51
|
+
location: str
|
|
52
|
+
fix: str
|
|
53
|
+
severity: str
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class ArsenalVerdict:
|
|
58
|
+
audit: str
|
|
59
|
+
score: int # 0-100
|
|
60
|
+
verified: bool
|
|
61
|
+
confidence: str # high | medium | low
|
|
62
|
+
findings: list[ArsenalFinding] = field(default_factory=list)
|
|
63
|
+
fix_plan: list[FixTask] = field(default_factory=list)
|
|
64
|
+
summary: str = ""
|
|
65
|
+
|
|
66
|
+
def to_dict(self) -> dict[str, Any]:
|
|
67
|
+
return {
|
|
68
|
+
"audit": self.audit, "score": self.score, "verified": self.verified,
|
|
69
|
+
"confidence": self.confidence, "summary": self.summary,
|
|
70
|
+
"findings": [f.__dict__ for f in self.findings],
|
|
71
|
+
"fix_plan": [t.__dict__ for t in self.fix_plan],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# --------------------------------------------------------------------------
|
|
76
|
+
# Audit definition — loaded from Agentik_SSOT/audits/<id>.yaml
|
|
77
|
+
# --------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class AuditPhase:
|
|
82
|
+
id: str
|
|
83
|
+
checks: str
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class GatherTool:
|
|
88
|
+
name: str
|
|
89
|
+
cmd: str
|
|
90
|
+
when: str = "*" # glob the tool applies to
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class Audit:
|
|
95
|
+
id: str
|
|
96
|
+
domain: str
|
|
97
|
+
question: str
|
|
98
|
+
weight: float = 1.0
|
|
99
|
+
threshold: int = 85
|
|
100
|
+
applies_roles: list[str] = field(default_factory=lambda: ["worker"])
|
|
101
|
+
applies_changed: list[str] = field(default_factory=lambda: ["*"])
|
|
102
|
+
gather: list[GatherTool] = field(default_factory=list)
|
|
103
|
+
phases: list[AuditPhase] = field(default_factory=list)
|
|
104
|
+
falsification: str = ""
|
|
105
|
+
fix_loop: bool = True
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def from_dict(d: dict) -> "Audit":
|
|
109
|
+
applies = d.get("applies_to", {}) or {}
|
|
110
|
+
return Audit(
|
|
111
|
+
id=d["id"], domain=d["domain"], question=d["question"],
|
|
112
|
+
weight=float(d.get("weight", 1.0)),
|
|
113
|
+
threshold=int(d.get("threshold", 85)),
|
|
114
|
+
applies_roles=list(applies.get("roles", ["worker"])),
|
|
115
|
+
applies_changed=list(applies.get("changed", ["*"])),
|
|
116
|
+
gather=[GatherTool(**g) for g in d.get("gather", [])],
|
|
117
|
+
phases=[AuditPhase(id=p["id"], checks=p["checks"])
|
|
118
|
+
for p in d.get("phases", [])],
|
|
119
|
+
falsification=d.get("falsification", ""),
|
|
120
|
+
fix_loop=bool(d.get("fix_loop", True)),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class AuditRegistry:
|
|
125
|
+
"""Loads every audit definition from Agentik_SSOT/audits/."""
|
|
126
|
+
|
|
127
|
+
def __init__(self, audits: dict[str, Audit]) -> None:
|
|
128
|
+
self._audits = audits
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def load(cls, audits_dir: str | Path) -> "AuditRegistry":
|
|
132
|
+
import yaml
|
|
133
|
+
audits: dict[str, Audit] = {}
|
|
134
|
+
for path in sorted(Path(audits_dir).glob("*.yaml")):
|
|
135
|
+
data = yaml.safe_load(path.read_text())
|
|
136
|
+
if data and data.get("id"):
|
|
137
|
+
audits[data["id"]] = Audit.from_dict(data)
|
|
138
|
+
return cls(audits)
|
|
139
|
+
|
|
140
|
+
def get(self, audit_id: str) -> Audit | None:
|
|
141
|
+
return self._audits.get(audit_id)
|
|
142
|
+
|
|
143
|
+
def all(self) -> list[Audit]:
|
|
144
|
+
return list(self._audits.values())
|
|
145
|
+
|
|
146
|
+
def select(self, role: str, changed: list[str]) -> list[Audit]:
|
|
147
|
+
"""The audits that apply to a task — by role and by changed-file glob."""
|
|
148
|
+
chosen: list[Audit] = []
|
|
149
|
+
for audit in self._audits.values():
|
|
150
|
+
if role not in audit.applies_roles:
|
|
151
|
+
continue
|
|
152
|
+
if audit.applies_changed == ["*"] or not changed:
|
|
153
|
+
chosen.append(audit)
|
|
154
|
+
continue
|
|
155
|
+
if any(fnmatch.fnmatch(f, pat)
|
|
156
|
+
for f in changed for pat in audit.applies_changed):
|
|
157
|
+
chosen.append(audit)
|
|
158
|
+
return chosen
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# --------------------------------------------------------------------------
|
|
162
|
+
# The forensic run — gather (deterministic) + falsify (agentic)
|
|
163
|
+
# --------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
_DOCTRINE = """\
|
|
166
|
+
You are a forensic investigator, not a friendly reviewer. The artifact is a
|
|
167
|
+
crime scene. Apply the Gestalt-Popper doctrine:
|
|
168
|
+
- GESTALT: first identify the hinge point — the one place that, if wrong, breaks
|
|
169
|
+
everything — and scrutinise it 10x.
|
|
170
|
+
- POPPER: do not verify that it works; prove where it LIES. Every name is a
|
|
171
|
+
claim, every message a promise, every type a contract. Find the divergence.
|
|
172
|
+
- ADVERSARIAL: assume a competitor wrote this. Bias toward FAIL. A perfect score
|
|
173
|
+
is earned by finding zero falsifiable claims, never by absence of effort.
|
|
174
|
+
Banned conclusions without evidence: "looks correct", "should be fine".
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def run_gather(audit: Audit, ctx: dict[str, Any]) -> list[dict[str, Any]]:
|
|
179
|
+
"""Run the audit's deterministic tools. Read-only, never raises."""
|
|
180
|
+
path = str(ctx.get("path", "."))
|
|
181
|
+
changed = ctx.get("changed", []) or []
|
|
182
|
+
results: list[dict[str, Any]] = []
|
|
183
|
+
for tool in audit.gather:
|
|
184
|
+
applies = tool.when == "*" or any(
|
|
185
|
+
fnmatch.fnmatch(f, pat)
|
|
186
|
+
for f in changed for pat in tool.when.split(","))
|
|
187
|
+
if changed and not applies:
|
|
188
|
+
results.append({"tool": tool.name, "status": "skipped",
|
|
189
|
+
"reason": "no matching changed files"})
|
|
190
|
+
continue
|
|
191
|
+
cmd = tool.cmd.replace("{path}", path)
|
|
192
|
+
try:
|
|
193
|
+
proc = subprocess.run(cmd, shell=True, capture_output=True,
|
|
194
|
+
text=True, timeout=180)
|
|
195
|
+
results.append({"tool": tool.name, "status": "ran",
|
|
196
|
+
"exit": proc.returncode,
|
|
197
|
+
"output": (proc.stdout or proc.stderr)[:4000]})
|
|
198
|
+
except Exception as exc: # noqa: BLE001 — gather must never break the gate
|
|
199
|
+
results.append({"tool": tool.name, "status": "error",
|
|
200
|
+
"reason": str(exc)[:200]})
|
|
201
|
+
return results
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def build_audit_prompt(audit: Audit, ctx: dict[str, Any],
|
|
205
|
+
gather: list[dict[str, Any]]) -> str:
|
|
206
|
+
"""Compose the agentic falsification prompt — shared shell + domain phases."""
|
|
207
|
+
phases = "\n".join(f" - {p.id}: {p.checks}" for p in audit.phases)
|
|
208
|
+
return (
|
|
209
|
+
f"{_DOCTRINE}\n"
|
|
210
|
+
f"AUDIT: {audit.id} — domain {audit.domain}\n"
|
|
211
|
+
f"THE QUESTION YOU MUST ANSWER: {audit.question}\n\n"
|
|
212
|
+
f"PHASES — investigate each:\n{phases}\n\n"
|
|
213
|
+
f"FALSIFICATION RULES: {audit.falsification}\n\n"
|
|
214
|
+
f"ARTIFACTS UNDER AUDIT:\n{json.dumps(ctx.get('artifacts', {}), indent=2)[:3000]}\n\n"
|
|
215
|
+
f"DETERMINISTIC GATHER FINDINGS:\n{json.dumps(gather, indent=2)[:3000]}\n\n"
|
|
216
|
+
f"Emit ONLY a JSON verdict: {{\"score\": 0-100, \"verified\": bool, "
|
|
217
|
+
f"\"confidence\": \"high|medium|low\", \"summary\": \"...\", "
|
|
218
|
+
f"\"findings\": [{{\"phase\",\"severity\",\"location\",\"claim\",\"reality\"}}], "
|
|
219
|
+
f"\"fix_plan\": [{{\"id\",\"finding\",\"location\",\"fix\",\"severity\"}}]}}. "
|
|
220
|
+
f"score >= {audit.threshold} means verified."
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _parse_verdict(audit: Audit, raw: dict[str, Any]) -> ArsenalVerdict:
|
|
225
|
+
findings = [
|
|
226
|
+
ArsenalFinding(
|
|
227
|
+
audit=audit.id, phase=f.get("phase", "?"),
|
|
228
|
+
severity=f.get("severity", "medium"),
|
|
229
|
+
location=f.get("location", "?"),
|
|
230
|
+
claim=f.get("claim", ""), reality=f.get("reality", ""),
|
|
231
|
+
category=f.get("category", "claim-vs-reality"),
|
|
232
|
+
) for f in raw.get("findings", [])
|
|
233
|
+
]
|
|
234
|
+
fixes = [
|
|
235
|
+
FixTask(id=t.get("id", f"FIX-{i+1}"), finding=t.get("finding", ""),
|
|
236
|
+
location=t.get("location", "?"), fix=t.get("fix", ""),
|
|
237
|
+
severity=t.get("severity", "medium"))
|
|
238
|
+
for i, t in enumerate(raw.get("fix_plan", []))
|
|
239
|
+
]
|
|
240
|
+
score = int(raw.get("score", 0))
|
|
241
|
+
return ArsenalVerdict(
|
|
242
|
+
audit=audit.id, score=score,
|
|
243
|
+
verified=bool(raw.get("verified", score >= audit.threshold)),
|
|
244
|
+
confidence=raw.get("confidence", "medium"),
|
|
245
|
+
findings=findings, fix_plan=fixes,
|
|
246
|
+
summary=raw.get("summary", ""),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def run_forensic_audit(audit: Audit, ctx: dict[str, Any],
|
|
251
|
+
provider: AgentProvider) -> ArsenalVerdict:
|
|
252
|
+
"""One forensic audit: deterministic gather + agentic falsification."""
|
|
253
|
+
gather = run_gather(audit, ctx)
|
|
254
|
+
prompt = build_audit_prompt(audit, ctx, gather)
|
|
255
|
+
result = provider.run(AgentRequest(role="audit", prompt=prompt, context=ctx))
|
|
256
|
+
raw = result.artifacts.get("verdict")
|
|
257
|
+
if raw is None:
|
|
258
|
+
try:
|
|
259
|
+
raw = json.loads(result.text)
|
|
260
|
+
except (json.JSONDecodeError, TypeError):
|
|
261
|
+
raw = {"score": 0, "verified": False, "confidence": "low",
|
|
262
|
+
"summary": "audit produced no parseable verdict"}
|
|
263
|
+
return _parse_verdict(audit, raw)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class ArsenalGate:
|
|
267
|
+
"""The verification gate, backed by the Quality Arsenal.
|
|
268
|
+
|
|
269
|
+
Drop-in for the executor: `verify(ctx)` returns an `AuditVerdict`, exactly
|
|
270
|
+
like the simple `AuditGate`. It always runs the mandatory runtime audit
|
|
271
|
+
(validate-live), then every forensic audit that applies to the task, and
|
|
272
|
+
aggregates. The rich per-audit verdicts are kept on `.last_verdicts` for the
|
|
273
|
+
mission report.
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
def __init__(self, registry: AuditRegistry, router: Any,
|
|
277
|
+
threshold: int = 85) -> None:
|
|
278
|
+
self._registry = registry
|
|
279
|
+
self._router = router
|
|
280
|
+
self._threshold = threshold
|
|
281
|
+
self.last_verdicts: list[ArsenalVerdict] = []
|
|
282
|
+
|
|
283
|
+
def verify(self, ctx: dict[str, Any]) -> AuditVerdict:
|
|
284
|
+
findings: list[AuditFinding] = []
|
|
285
|
+
scores: list[tuple[float, int]] = [] # (weight, score)
|
|
286
|
+
|
|
287
|
+
# 1. mandatory runtime audit — validate-live, never skipped
|
|
288
|
+
runtime = audit_runtime_flow(ctx)
|
|
289
|
+
findings.append(AuditFinding(runtime.audit, runtime.score, runtime.detail))
|
|
290
|
+
if runtime.score == 0:
|
|
291
|
+
# the flow does not run — cannot be VERIFIED, full stop
|
|
292
|
+
self.last_verdicts = []
|
|
293
|
+
return AuditVerdict(score=0, verified=False, findings=findings)
|
|
294
|
+
scores.append((1.0, runtime.score))
|
|
295
|
+
|
|
296
|
+
# 2. every forensic audit that applies
|
|
297
|
+
provider = self._router.resolve("audit")
|
|
298
|
+
selected = self._registry.select(
|
|
299
|
+
role=str(ctx.get("role", "worker")),
|
|
300
|
+
changed=ctx.get("changed", []) or [],
|
|
301
|
+
)
|
|
302
|
+
self.last_verdicts = []
|
|
303
|
+
for audit in selected:
|
|
304
|
+
verdict = run_forensic_audit(audit, ctx, provider)
|
|
305
|
+
self.last_verdicts.append(verdict)
|
|
306
|
+
scores.append((audit.weight, verdict.score))
|
|
307
|
+
findings.append(AuditFinding(
|
|
308
|
+
audit.id, verdict.score,
|
|
309
|
+
f"{verdict.summary} ({len(verdict.findings)} findings)"))
|
|
310
|
+
|
|
311
|
+
total_w = sum(w for w, _ in scores)
|
|
312
|
+
agg = round(sum(w * s for w, s in scores) / total_w) if total_w else 0
|
|
313
|
+
return AuditVerdict(
|
|
314
|
+
score=agg, verified=agg >= self._threshold, findings=findings)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""The join barrier — structured concurrency for agent graphs.
|
|
2
|
+
|
|
3
|
+
A dispatcher owns a scope. The scope cannot be JOINABLE until every child is in
|
|
4
|
+
a terminal state. "Parent done when all children done" is therefore not
|
|
5
|
+
something the engine *detects* — it is a mechanical invariant.
|
|
6
|
+
|
|
7
|
+
This is the structural fix for the "parent finished before its children" bug:
|
|
8
|
+
a dispatcher may only transition toward COMPLETED on receiving `scope.joinable`,
|
|
9
|
+
and the barrier — not the dispatcher — decides when that event fires.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import Sequence
|
|
15
|
+
|
|
16
|
+
from omega_engine.task import TERMINAL, TaskState
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ScopeStatus(str, Enum):
|
|
20
|
+
RUNNING = "running" # ≥1 child still alive — the parent CANNOT close
|
|
21
|
+
PARTIAL = "partial" # all children terminal, but ≥1 FAILED
|
|
22
|
+
JOINABLE = "joinable" # all children terminal and none FAILED — parent unblocked
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def scope_status(children: Sequence[TaskState]) -> ScopeStatus:
|
|
26
|
+
"""Derive a scope's status from its children's states. Pure.
|
|
27
|
+
|
|
28
|
+
- empty scope → JOINABLE (trivially)
|
|
29
|
+
- any non-terminal child → RUNNING
|
|
30
|
+
- all terminal, ≥1 FAILED→ PARTIAL
|
|
31
|
+
- all terminal, none FAILED → JOINABLE
|
|
32
|
+
|
|
33
|
+
PARTIAL is deliberately distinct from JOINABLE. When a scope is PARTIAL the
|
|
34
|
+
dispatcher must apply an explicit, per-topology policy — retry the failed
|
|
35
|
+
child, accept the partial result, or fail upward. That policy is declared in
|
|
36
|
+
Agentik_Orchestration/topologies/<name>.yaml under `on_partial:`, never
|
|
37
|
+
hard-coded in the engine.
|
|
38
|
+
"""
|
|
39
|
+
if not children:
|
|
40
|
+
return ScopeStatus.JOINABLE
|
|
41
|
+
if any(s not in TERMINAL for s in children):
|
|
42
|
+
return ScopeStatus.RUNNING
|
|
43
|
+
if any(s is TaskState.FAILED for s in children):
|
|
44
|
+
return ScopeStatus.PARTIAL
|
|
45
|
+
return ScopeStatus.JOINABLE
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""The event bus — push, not poll (principle 5).
|
|
2
|
+
|
|
3
|
+
The bus is the live path: a task emits an event, the bus appends it to the store
|
|
4
|
+
(the durable source of truth) and pushes it to every subscriber synchronously.
|
|
5
|
+
Subscribers are the progress tracker, the Telegram bridge, the supervisor.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Callable
|
|
10
|
+
|
|
11
|
+
from omega_engine.events import Event
|
|
12
|
+
from omega_engine.store import EventStore
|
|
13
|
+
|
|
14
|
+
Subscriber = Callable[[Event], None]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EventBus:
|
|
18
|
+
"""Appends every event to the store, then pushes it to all subscribers.
|
|
19
|
+
|
|
20
|
+
Synchronous and in-process — simple and correct. A Redis pub/sub bus can
|
|
21
|
+
replace this behind the same `publish` / `subscribe` surface for multi-process
|
|
22
|
+
deployments.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, store: EventStore) -> None:
|
|
26
|
+
self._store = store
|
|
27
|
+
self._subscribers: list[Subscriber] = []
|
|
28
|
+
|
|
29
|
+
def subscribe(self, fn: Subscriber) -> None:
|
|
30
|
+
"""Register a callback invoked for every published event."""
|
|
31
|
+
self._subscribers.append(fn)
|
|
32
|
+
|
|
33
|
+
def publish(self, event: Event) -> Event:
|
|
34
|
+
"""Persist the event, then notify subscribers. Returns the event.
|
|
35
|
+
|
|
36
|
+
A failing subscriber must never corrupt the log — its exception is
|
|
37
|
+
swallowed (the event is already durably stored).
|
|
38
|
+
"""
|
|
39
|
+
self._store.append(event)
|
|
40
|
+
for fn in list(self._subscribers):
|
|
41
|
+
try:
|
|
42
|
+
fn(event)
|
|
43
|
+
except Exception: # noqa: BLE001 — a bad subscriber must not break the bus
|
|
44
|
+
pass
|
|
45
|
+
return event
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""The `omega` command-line interface.
|
|
2
|
+
|
|
3
|
+
A thin entry point. The installer's doctor step calls `omega doctor`; operators
|
|
4
|
+
use `omega status` to see every task's derived state.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from omega_engine import __version__
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _omega_home() -> Path:
|
|
17
|
+
return Path(os.environ.get("OMEGA_HOME", str(Path.home() / "Omega")))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def cmd_version(_args: argparse.Namespace) -> int:
|
|
21
|
+
print(f"omega-engine {__version__}")
|
|
22
|
+
return 0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def cmd_doctor(_args: argparse.Namespace) -> int:
|
|
26
|
+
"""Validate an Omega OS deployment: the 8-block tree + the event store."""
|
|
27
|
+
home = _omega_home()
|
|
28
|
+
blocks = [
|
|
29
|
+
"Agentik_SSOT", "Agentik_Engine", "Agentik_Orchestration",
|
|
30
|
+
"Agentik_Providers", "Agentik_Coding", "Agentik_Tools",
|
|
31
|
+
"Agentik_Runtime", "Agentik_Extra",
|
|
32
|
+
]
|
|
33
|
+
ok = True
|
|
34
|
+
print(f"omega doctor — OMEGA_HOME={home}")
|
|
35
|
+
for b in blocks:
|
|
36
|
+
present = (home / b).is_dir()
|
|
37
|
+
print(f" [{'ok' if present else 'MISSING'}] {b}")
|
|
38
|
+
ok = ok and present
|
|
39
|
+
store = home / "Agentik_Runtime" / "eventlog" / "omega.db"
|
|
40
|
+
print(f" [{'ok' if store.exists() else 'pending'}] event store: {store}")
|
|
41
|
+
print("doctor: PASS" if ok else "doctor: FAIL")
|
|
42
|
+
return 0 if ok else 1
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def cmd_status(_args: argparse.Namespace) -> int:
|
|
46
|
+
"""Show every task and its derived state — the event log is the truth."""
|
|
47
|
+
from omega_engine.reducer import reduce_task
|
|
48
|
+
from omega_engine.store import SQLiteStore
|
|
49
|
+
|
|
50
|
+
db = _omega_home() / "Agentik_Runtime" / "eventlog" / "omega.db"
|
|
51
|
+
if not db.exists():
|
|
52
|
+
print("no event store yet — nothing running")
|
|
53
|
+
return 0
|
|
54
|
+
store = SQLiteStore(db)
|
|
55
|
+
ids = store.task_ids()
|
|
56
|
+
if not ids:
|
|
57
|
+
print("event store is empty")
|
|
58
|
+
return 0
|
|
59
|
+
for task_id in sorted(ids):
|
|
60
|
+
state = reduce_task(store.events_for(task_id))
|
|
61
|
+
print(f" {state.value:<13} {task_id}")
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def cmd_account(_args: argparse.Namespace) -> int:
|
|
66
|
+
"""Show the Claude Code Max account pool and the selection strategy.
|
|
67
|
+
|
|
68
|
+
Omega OS runs one engine, not N tmux sessions — so an account is not
|
|
69
|
+
"switched" globally. The Claude provider holds a POOL and distributes agent
|
|
70
|
+
calls across accounts. Add one with `omega account login`.
|
|
71
|
+
See docs/ACCOUNT-AND-BILLING.md.
|
|
72
|
+
"""
|
|
73
|
+
cfg = _omega_home() / "Agentik_Providers" / "claude" / "accounts.yaml"
|
|
74
|
+
if not cfg.exists():
|
|
75
|
+
cfg = cfg.with_name("accounts.example.yaml")
|
|
76
|
+
if not cfg.exists():
|
|
77
|
+
print("no Claude Max account pool configured — see docs/ACCOUNT-AND-BILLING.md")
|
|
78
|
+
return 0
|
|
79
|
+
try:
|
|
80
|
+
import yaml
|
|
81
|
+
except ImportError:
|
|
82
|
+
print(f"account pool config: {cfg} (install pyyaml to render it)")
|
|
83
|
+
return 0
|
|
84
|
+
data = yaml.safe_load(cfg.read_text()) or {}
|
|
85
|
+
pool = data.get("pool", [])
|
|
86
|
+
print(f"Claude Max account pool — selection: {data.get('selection', 'least-used')}")
|
|
87
|
+
for a in pool or [{"status": "(empty)", "id": "", "label": ""}]:
|
|
88
|
+
print(f" [{str(a.get('status', '?')):<9}] {str(a.get('id', '')):<16} {a.get('label', '')}")
|
|
89
|
+
print(" add an account: omega account login (docs/ACCOUNT-AND-BILLING.md)")
|
|
90
|
+
return 0
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def cmd_billing(_args: argparse.Namespace) -> int:
|
|
94
|
+
"""Show usage and cost per Claude Max account.
|
|
95
|
+
|
|
96
|
+
Per-account billing reads token usage recorded on the event log against the
|
|
97
|
+
provider cost model — so you see which account is near its weekly limit.
|
|
98
|
+
"""
|
|
99
|
+
print("omega billing — usage per Claude Max account")
|
|
100
|
+
print(" source: token usage on task.* events + the provider cost model")
|
|
101
|
+
print(" build-out: live per-account aggregation — see docs/ACCOUNT-AND-BILLING.md")
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def cmd_run(args: argparse.Namespace) -> int:
|
|
106
|
+
"""Run a mission end-to-end — Oracle plans, workers execute, the audit gate
|
|
107
|
+
verifies, and a whitepaper PDF report is produced."""
|
|
108
|
+
from omega_engine.mission import run_mission
|
|
109
|
+
|
|
110
|
+
outcome = run_mission(args.intent)
|
|
111
|
+
print(f"mission {outcome.result.mission_id}: "
|
|
112
|
+
f"{outcome.result.final_state.value} ({outcome.progress_pct}%)")
|
|
113
|
+
if outcome.report_pdf:
|
|
114
|
+
print(f"report: {outcome.report_pdf}")
|
|
115
|
+
return 0 if outcome.result.verified else 1
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def cmd_project(args: argparse.Namespace) -> int:
|
|
119
|
+
"""Create a project — folder, registry entry, and (if configured) a bound
|
|
120
|
+
Telegram topic."""
|
|
121
|
+
from omega_engine.project import create_project
|
|
122
|
+
|
|
123
|
+
telegram = None
|
|
124
|
+
if not args.no_telegram:
|
|
125
|
+
try:
|
|
126
|
+
from omega_engine.telegram import TelegramBridge
|
|
127
|
+
telegram = TelegramBridge.from_vault()
|
|
128
|
+
except Exception as exc: # noqa: BLE001
|
|
129
|
+
print(f"(no Telegram topic — {exc})")
|
|
130
|
+
project = create_project(args.name, telegram=telegram)
|
|
131
|
+
print(f"project created: {project.slug} -> {project.path}")
|
|
132
|
+
if project.topic_id:
|
|
133
|
+
print(f"telegram topic: {project.topic_id}")
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def main(argv: list[str] | None = None) -> int:
|
|
138
|
+
parser = argparse.ArgumentParser(prog="omega", description="Omega OS control CLI")
|
|
139
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
140
|
+
sub.add_parser("version", help="print the engine version").set_defaults(fn=cmd_version)
|
|
141
|
+
sub.add_parser("doctor", help="validate the deployment").set_defaults(fn=cmd_doctor)
|
|
142
|
+
sub.add_parser("status", help="show all tasks and their derived state").set_defaults(fn=cmd_status)
|
|
143
|
+
sub.add_parser("account", help="show the Claude Max account pool").set_defaults(fn=cmd_account)
|
|
144
|
+
sub.add_parser("billing", help="show usage/cost per Claude Max account").set_defaults(fn=cmd_billing)
|
|
145
|
+
p_run = sub.add_parser("run", help="run a mission end-to-end")
|
|
146
|
+
p_run.add_argument("intent", help="the mission, in natural language")
|
|
147
|
+
p_run.set_defaults(fn=cmd_run)
|
|
148
|
+
p_proj = sub.add_parser("project", help="create a project")
|
|
149
|
+
p_proj.add_argument("name", help="the project name")
|
|
150
|
+
p_proj.add_argument("--no-telegram", action="store_true",
|
|
151
|
+
help="skip Telegram topic creation")
|
|
152
|
+
p_proj.set_defaults(fn=cmd_project)
|
|
153
|
+
args = parser.parse_args(argv)
|
|
154
|
+
return args.fn(args)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
if __name__ == "__main__":
|
|
158
|
+
sys.exit(main())
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Immutable events — the single source of truth.
|
|
2
|
+
|
|
3
|
+
The state of any task is a fold over its events. No agent ever writes a state;
|
|
4
|
+
agents only append events. See omega_engine.reducer.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import time
|
|
10
|
+
import uuid
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EventType(str, Enum):
|
|
17
|
+
# --- task lifecycle — these drive the reducer ---
|
|
18
|
+
CREATED = "task.created"
|
|
19
|
+
DISPATCHED = "task.dispatched"
|
|
20
|
+
STARTED = "task.started"
|
|
21
|
+
CLAIMED_DONE = "task.claimed_done"
|
|
22
|
+
VERIFYING = "task.verifying"
|
|
23
|
+
VERIFIED = "task.verified"
|
|
24
|
+
REJECTED = "task.rejected"
|
|
25
|
+
COMPLETED = "task.completed"
|
|
26
|
+
FAILED = "task.failed"
|
|
27
|
+
HEARTBEAT = "task.heartbeat" # liveness ping — no state change
|
|
28
|
+
# --- scope event — consumed by the barrier, not the task reducer ---
|
|
29
|
+
SCOPE_JOINABLE = "scope.joinable"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class Event:
|
|
34
|
+
"""An immutable fact. Append-only. Never mutated, never deleted."""
|
|
35
|
+
|
|
36
|
+
task_id: str
|
|
37
|
+
type: EventType
|
|
38
|
+
payload: dict[str, Any] = field(default_factory=dict)
|
|
39
|
+
id: str = field(default_factory=lambda: uuid.uuid4().hex)
|
|
40
|
+
ts: float = field(default_factory=time.time)
|
|
41
|
+
|
|
42
|
+
def to_row(self) -> dict[str, Any]:
|
|
43
|
+
"""Flatten to a storable row (payload JSON-encoded)."""
|
|
44
|
+
return {
|
|
45
|
+
"id": self.id,
|
|
46
|
+
"task_id": self.task_id,
|
|
47
|
+
"type": self.type.value,
|
|
48
|
+
"ts": self.ts,
|
|
49
|
+
"payload": json.dumps(self.payload, ensure_ascii=False),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def from_row(row: dict[str, Any]) -> "Event":
|
|
54
|
+
return Event(
|
|
55
|
+
id=row["id"],
|
|
56
|
+
task_id=row["task_id"],
|
|
57
|
+
type=EventType(row["type"]),
|
|
58
|
+
ts=row["ts"],
|
|
59
|
+
payload=json.loads(row["payload"]) if row.get("payload") else {},
|
|
60
|
+
)
|