@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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/bin/omega-os.js +48 -0
  4. package/bootstrap/lib/common.sh +73 -0
  5. package/bootstrap/lib/steps.sh +153 -0
  6. package/bootstrap/manifest.example.yaml +45 -0
  7. package/docs/ACCOUNT-AND-BILLING.md +95 -0
  8. package/docs/ARCHITECTURE.md +225 -0
  9. package/docs/AUTONOMOUS-AGENTS.md +128 -0
  10. package/docs/ENGINE-SPEC.md +174 -0
  11. package/docs/INSTALL.md +106 -0
  12. package/docs/MCP-AND-PLUGINS.md +121 -0
  13. package/docs/RUNTIME-PLAN.md +63 -0
  14. package/install.sh +54 -0
  15. package/omega/Agentik_Coding/README.md +21 -0
  16. package/omega/Agentik_Engine/README.md +58 -0
  17. package/omega/Agentik_Engine/omega_engine/__init__.py +58 -0
  18. package/omega/Agentik_Engine/omega_engine/__pycache__/__init__.cpython-313.pyc +0 -0
  19. package/omega/Agentik_Engine/omega_engine/__pycache__/audit.cpython-313.pyc +0 -0
  20. package/omega/Agentik_Engine/omega_engine/__pycache__/audit_arsenal.cpython-313.pyc +0 -0
  21. package/omega/Agentik_Engine/omega_engine/__pycache__/barrier.cpython-313.pyc +0 -0
  22. package/omega/Agentik_Engine/omega_engine/__pycache__/bus.cpython-313.pyc +0 -0
  23. package/omega/Agentik_Engine/omega_engine/__pycache__/cli.cpython-313.pyc +0 -0
  24. package/omega/Agentik_Engine/omega_engine/__pycache__/events.cpython-313.pyc +0 -0
  25. package/omega/Agentik_Engine/omega_engine/__pycache__/executor.cpython-313.pyc +0 -0
  26. package/omega/Agentik_Engine/omega_engine/__pycache__/mission.cpython-313.pyc +0 -0
  27. package/omega/Agentik_Engine/omega_engine/__pycache__/progress.cpython-313.pyc +0 -0
  28. package/omega/Agentik_Engine/omega_engine/__pycache__/project.cpython-313.pyc +0 -0
  29. package/omega/Agentik_Engine/omega_engine/__pycache__/provider.cpython-313.pyc +0 -0
  30. package/omega/Agentik_Engine/omega_engine/__pycache__/reducer.cpython-313.pyc +0 -0
  31. package/omega/Agentik_Engine/omega_engine/__pycache__/report.cpython-313.pyc +0 -0
  32. package/omega/Agentik_Engine/omega_engine/__pycache__/router.cpython-313.pyc +0 -0
  33. package/omega/Agentik_Engine/omega_engine/__pycache__/store.cpython-313.pyc +0 -0
  34. package/omega/Agentik_Engine/omega_engine/__pycache__/supervisor.cpython-313.pyc +0 -0
  35. package/omega/Agentik_Engine/omega_engine/__pycache__/task.cpython-313.pyc +0 -0
  36. package/omega/Agentik_Engine/omega_engine/__pycache__/telegram.cpython-313.pyc +0 -0
  37. package/omega/Agentik_Engine/omega_engine/audit.py +96 -0
  38. package/omega/Agentik_Engine/omega_engine/audit_arsenal.py +314 -0
  39. package/omega/Agentik_Engine/omega_engine/barrier.py +45 -0
  40. package/omega/Agentik_Engine/omega_engine/bus.py +45 -0
  41. package/omega/Agentik_Engine/omega_engine/cli.py +158 -0
  42. package/omega/Agentik_Engine/omega_engine/events.py +60 -0
  43. package/omega/Agentik_Engine/omega_engine/executor.py +167 -0
  44. package/omega/Agentik_Engine/omega_engine/mission.py +145 -0
  45. package/omega/Agentik_Engine/omega_engine/progress.py +75 -0
  46. package/omega/Agentik_Engine/omega_engine/project.py +92 -0
  47. package/omega/Agentik_Engine/omega_engine/provider.py +139 -0
  48. package/omega/Agentik_Engine/omega_engine/reducer.py +76 -0
  49. package/omega/Agentik_Engine/omega_engine/report.py +146 -0
  50. package/omega/Agentik_Engine/omega_engine/router.py +34 -0
  51. package/omega/Agentik_Engine/omega_engine/store.py +97 -0
  52. package/omega/Agentik_Engine/omega_engine/supervisor.py +69 -0
  53. package/omega/Agentik_Engine/omega_engine/task.py +91 -0
  54. package/omega/Agentik_Engine/omega_engine/telegram.py +115 -0
  55. package/omega/Agentik_Engine/pyproject.toml +31 -0
  56. package/omega/Agentik_Engine/tests/__pycache__/test_audit_arsenal.cpython-313.pyc +0 -0
  57. package/omega/Agentik_Engine/tests/__pycache__/test_executor.cpython-313.pyc +0 -0
  58. package/omega/Agentik_Engine/tests/__pycache__/test_mission.cpython-313.pyc +0 -0
  59. package/omega/Agentik_Engine/tests/__pycache__/test_progress.cpython-313.pyc +0 -0
  60. package/omega/Agentik_Engine/tests/__pycache__/test_project.cpython-313.pyc +0 -0
  61. package/omega/Agentik_Engine/tests/__pycache__/test_reducer.cpython-313.pyc +0 -0
  62. package/omega/Agentik_Engine/tests/__pycache__/test_report.cpython-313.pyc +0 -0
  63. package/omega/Agentik_Engine/tests/test_audit_arsenal.py +80 -0
  64. package/omega/Agentik_Engine/tests/test_executor.py +96 -0
  65. package/omega/Agentik_Engine/tests/test_mission.py +64 -0
  66. package/omega/Agentik_Engine/tests/test_progress.py +69 -0
  67. package/omega/Agentik_Engine/tests/test_project.py +61 -0
  68. package/omega/Agentik_Engine/tests/test_reducer.py +144 -0
  69. package/omega/Agentik_Engine/tests/test_report.py +88 -0
  70. package/omega/Agentik_Extra/README.md +37 -0
  71. package/omega/Agentik_Extra/etc/agentik.env.example +19 -0
  72. package/omega/Agentik_Extra/etc/structure.yaml +46 -0
  73. package/omega/Agentik_Orchestration/README.md +43 -0
  74. package/omega/Agentik_Orchestration/autonomous/README.md +29 -0
  75. package/omega/Agentik_Orchestration/autonomous/example-agents.yaml +85 -0
  76. package/omega/Agentik_Orchestration/educators/README.md +55 -0
  77. package/omega/Agentik_Orchestration/topologies/aisb-oracle-worker.yaml +42 -0
  78. package/omega/Agentik_Orchestration/verifier/audit-router.yaml +26 -0
  79. package/omega/Agentik_Providers/README.md +62 -0
  80. package/omega/Agentik_Providers/claude/accounts.example.yaml +28 -0
  81. package/omega/Agentik_Providers/registry.yaml +30 -0
  82. package/omega/Agentik_Runtime/README.md +30 -0
  83. package/omega/Agentik_SSOT/README.md +36 -0
  84. package/omega/Agentik_SSOT/VERSION +1 -0
  85. package/omega/Agentik_SSOT/audits/a11yaudit.yaml +69 -0
  86. package/omega/Agentik_SSOT/audits/apiaudit.yaml +71 -0
  87. package/omega/Agentik_SSOT/audits/automationaudit.yaml +77 -0
  88. package/omega/Agentik_SSOT/audits/codeaudit.yaml +63 -0
  89. package/omega/Agentik_SSOT/audits/copyaudit.yaml +68 -0
  90. package/omega/Agentik_SSOT/audits/dataaudit.yaml +76 -0
  91. package/omega/Agentik_SSOT/audits/debugaudit.yaml +75 -0
  92. package/omega/Agentik_SSOT/audits/dxaudit.yaml +78 -0
  93. package/omega/Agentik_SSOT/audits/featureaudit.yaml +73 -0
  94. package/omega/Agentik_SSOT/audits/flowaudit.yaml +72 -0
  95. package/omega/Agentik_SSOT/audits/logicaudit.yaml +75 -0
  96. package/omega/Agentik_SSOT/audits/motionaudit.yaml +67 -0
  97. package/omega/Agentik_SSOT/audits/perfaudit.yaml +71 -0
  98. package/omega/Agentik_SSOT/audits/refontaudit.yaml +77 -0
  99. package/omega/Agentik_SSOT/audits/retentionaudit.yaml +84 -0
  100. package/omega/Agentik_SSOT/audits/secaudit.yaml +73 -0
  101. package/omega/Agentik_SSOT/audits/seoaudit.yaml +75 -0
  102. package/omega/Agentik_SSOT/audits/uiuxaudit.yaml +61 -0
  103. package/omega/Agentik_SSOT/mcp/mcp-catalog.yaml +136 -0
  104. package/omega/Agentik_SSOT/rules/constitution.md +44 -0
  105. package/omega/Agentik_SSOT/schemas/event.schema.json +45 -0
  106. package/omega/Agentik_SSOT/schemas/task.schema.json +54 -0
  107. package/omega/Agentik_Tools/README.md +42 -0
  108. package/omega/Agentik_Tools/registry.json +15 -0
  109. package/package.json +43 -0
@@ -0,0 +1,167 @@
1
+ """The topology executor — what makes OmegaOS run a mission.
2
+
3
+ It walks a dispatcher/executor tree. A dispatcher calls its provider for a plan
4
+ and recurses into children; a worker runs its provider, then the audit gate
5
+ verifies it. The join barrier governs every dispatcher — it can only complete
6
+ when all children are terminal and none failed.
7
+
8
+ Every state change is an event on the bus. The reducer derives state; the
9
+ executor never writes a state directly.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import uuid
14
+ from dataclasses import dataclass
15
+
16
+ from omega_engine.audit import AuditGate
17
+ from omega_engine.barrier import ScopeStatus, scope_status
18
+ from omega_engine.bus import EventBus
19
+ from omega_engine.events import Event, EventType
20
+ from omega_engine.provider import AgentRequest
21
+ from omega_engine.reducer import reduce_task
22
+ from omega_engine.router import ModelRouter
23
+ from omega_engine.store import EventStore
24
+ from omega_engine.task import Kind, Task, TaskState
25
+
26
+ #: Roles that are leaf executors. Anything else is a dispatcher.
27
+ EXECUTOR_ROLES = {"worker"}
28
+
29
+
30
+ @dataclass
31
+ class MissionResult:
32
+ mission_id: str
33
+ intent: str
34
+ root: Task
35
+ tasks: dict[str, Task]
36
+ final_state: TaskState
37
+
38
+ @property
39
+ def verified(self) -> bool:
40
+ return self.final_state is TaskState.COMPLETED
41
+
42
+
43
+ class Executor:
44
+ """Runs a mission graph to completion, with verified completion throughout."""
45
+
46
+ def __init__(
47
+ self,
48
+ store: EventStore,
49
+ bus: EventBus,
50
+ router: ModelRouter,
51
+ audit: AuditGate,
52
+ ) -> None:
53
+ self._store = store
54
+ self._bus = bus
55
+ self._router = router
56
+ self._audit = audit
57
+ self._tasks: dict[str, Task] = {}
58
+
59
+ # -- public ---------------------------------------------------------------
60
+ def run_mission(self, intent: str, root_role: str = "oracle") -> MissionResult:
61
+ """Run a mission from a natural-language intent. Synchronous, correct."""
62
+ mission_id = "m-" + uuid.uuid4().hex[:8]
63
+ root = self._new_task(
64
+ kind=Kind.DISPATCHER, role=root_role,
65
+ spec={"intent": intent}, parent_id=None, mission_id=mission_id,
66
+ )
67
+ self._run(root, mission_id)
68
+ return MissionResult(
69
+ mission_id=mission_id, intent=intent, root=root,
70
+ tasks=dict(self._tasks), final_state=self._state(root.id),
71
+ )
72
+
73
+ # -- internals ------------------------------------------------------------
74
+ def _new_task(self, *, kind: Kind, role: str, spec: dict,
75
+ parent_id: str | None, mission_id: str) -> Task:
76
+ prefix = "t-" if kind is Kind.EXECUTOR else "d-"
77
+ tid = prefix + uuid.uuid4().hex[:8]
78
+ task = Task(
79
+ id=tid, kind=kind, role=role,
80
+ spec=dict(spec, mission_id=mission_id), parent_id=parent_id,
81
+ )
82
+ self._tasks[tid] = task
83
+ return task
84
+
85
+ def _emit(self, task_id: str, etype: EventType, payload: dict | None = None) -> None:
86
+ self._bus.publish(Event(task_id=task_id, type=etype, payload=payload or {}))
87
+
88
+ def _state(self, task_id: str) -> TaskState:
89
+ return reduce_task(self._store.events_for(task_id))
90
+
91
+ def _run(self, task: Task, mission_id: str) -> None:
92
+ self._emit(task.id, EventType.CREATED,
93
+ {"role": task.role, "kind": task.kind.value, "mission_id": mission_id})
94
+ self._emit(task.id, EventType.DISPATCHED)
95
+ self._emit(task.id, EventType.STARTED)
96
+ if task.kind is Kind.EXECUTOR:
97
+ self._run_worker(task)
98
+ else:
99
+ self._run_dispatcher(task, mission_id)
100
+ task.state = self._state(task.id)
101
+
102
+ def _run_dispatcher(self, task: Task, mission_id: str) -> None:
103
+ provider = self._router.resolve(task.role)
104
+ prompt = task.spec.get("intent") or task.spec.get("task") or task.role
105
+ result = provider.run(
106
+ AgentRequest(role=task.role, prompt=str(prompt), context=task.spec)
107
+ )
108
+ children: list[Task] = []
109
+ for item in result.plan:
110
+ child_role = item.get("role", "worker")
111
+ child_kind = (
112
+ Kind.EXECUTOR if child_role in EXECUTOR_ROLES else Kind.DISPATCHER
113
+ )
114
+ child = self._new_task(
115
+ kind=child_kind, role=child_role,
116
+ spec=item.get("spec", {}), parent_id=task.id, mission_id=mission_id,
117
+ )
118
+ self._run(child, mission_id)
119
+ children.append(child)
120
+
121
+ status = scope_status([self._state(c.id) for c in children])
122
+ if status is ScopeStatus.JOINABLE:
123
+ # the barrier resolved — the dispatcher may now close
124
+ self._emit(task.id, EventType.SCOPE_JOINABLE, {"children": len(children)})
125
+ self._emit(task.id, EventType.CLAIMED_DONE)
126
+ self._emit(task.id, EventType.VERIFYING)
127
+ self._emit(task.id, EventType.VERIFIED, {"reason": "all children verified"})
128
+ self._emit(task.id, EventType.COMPLETED)
129
+ else:
130
+ # PARTIAL — a child failed. on_partial = fail_up (children already
131
+ # retried within their own budget). Honest partial failure.
132
+ failed = [c.id for c in children
133
+ if self._state(c.id) is TaskState.FAILED]
134
+ self._emit(task.id, EventType.FAILED,
135
+ {"reason": "partial scope", "failed_children": failed})
136
+
137
+ def _run_worker(self, task: Task) -> None:
138
+ provider = self._router.resolve(task.role)
139
+ for attempt in range(task.budget.max_iterations):
140
+ result = provider.run(AgentRequest(
141
+ role=task.role,
142
+ prompt=str(task.spec.get("task", task.role)),
143
+ context=task.spec,
144
+ ))
145
+ self._emit(task.id, EventType.CLAIMED_DONE, {"attempt": attempt + 1})
146
+ self._emit(task.id, EventType.VERIFYING)
147
+ verdict = self._audit.verify({
148
+ "artifacts": result.artifacts,
149
+ "runtime_cmd": task.spec.get("verify_cmd"),
150
+ "role": task.role,
151
+ "changed": result.artifacts.get("files", []),
152
+ "path": str(task.spec.get("path", ".")),
153
+ })
154
+ if verdict.verified:
155
+ self._emit(task.id, EventType.VERIFIED, {
156
+ "score": verdict.score,
157
+ "findings": [f.audit for f in verdict.findings],
158
+ })
159
+ self._emit(task.id, EventType.COMPLETED)
160
+ return
161
+ self._emit(task.id, EventType.REJECTED,
162
+ {"score": verdict.score, "attempt": attempt + 1})
163
+ if attempt + 1 < task.budget.max_iterations:
164
+ self._emit(task.id, EventType.DISPATCHED, {"retry": attempt + 2})
165
+ self._emit(task.id, EventType.STARTED)
166
+ # budget exhausted — honest failure, the deadman clause terminates it
167
+ self._emit(task.id, EventType.FAILED, {"reason": "audit budget exhausted"})
@@ -0,0 +1,145 @@
1
+ """Mission coordinator — the entry point that ties the runtime together.
2
+
3
+ `run_mission()` executes a mission, tracks progress live to a Telegram topic
4
+ (a progress bar that updates in place, throttled to respect flood control), and
5
+ ALWAYS produces the Oracle's whitepaper PDF report — delivered into the topic.
6
+
7
+ Telegram is best-effort: a delivery hiccup never fails a mission that ran fine.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import time
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+
16
+ from omega_engine.audit import AuditGate
17
+ from omega_engine.bus import EventBus
18
+ from omega_engine.executor import Executor, MissionResult
19
+ from omega_engine.progress import ProgressTracker
20
+ from omega_engine.provider import ClaudeProvider, MockProvider
21
+ from omega_engine.report import generate_mission_report
22
+ from omega_engine.router import ModelRouter
23
+ from omega_engine.store import SQLiteStore
24
+
25
+ #: minimum seconds between live progress edits — Telegram flood control
26
+ _EDIT_THROTTLE_S = 3.0
27
+
28
+
29
+ def _omega_home(explicit: str | Path | None = None) -> Path:
30
+ return Path(explicit or os.environ.get("OMEGA_HOME", str(Path.home() / "Omega")))
31
+
32
+
33
+ def build_router() -> ModelRouter:
34
+ """Real Claude when a key is present, else the deterministic MockProvider —
35
+ so `omega run` always works, with or without credentials."""
36
+ use_claude = (
37
+ os.environ.get("ANTHROPIC_API_KEY")
38
+ and os.environ.get("OMEGA_PROVIDER", "") != "mock"
39
+ )
40
+ if use_claude:
41
+ try:
42
+ import anthropic # noqa: F401
43
+ return ModelRouter.single(ClaudeProvider())
44
+ except ImportError:
45
+ pass
46
+ return ModelRouter.single(MockProvider())
47
+
48
+
49
+ @dataclass
50
+ class MissionOutcome:
51
+ result: MissionResult
52
+ report_pdf: Path | None
53
+ progress_pct: int
54
+
55
+
56
+ def run_mission(
57
+ intent: str,
58
+ *,
59
+ omega_home: str | Path | None = None,
60
+ topic_id: int | None = None,
61
+ telegram: object | None = None,
62
+ report_dir: str | Path | None = None,
63
+ ) -> MissionOutcome:
64
+ """Run a mission end-to-end. Always emits the Oracle's PDF report."""
65
+ home = _omega_home(omega_home)
66
+ store = SQLiteStore(home / "Agentik_Runtime" / "eventlog" / "omega.db")
67
+ bus = EventBus(store)
68
+ tracker = ProgressTracker()
69
+ bus.subscribe(tracker.on_event)
70
+
71
+ # live Telegram progress — a message that edits itself as work advances,
72
+ # throttled so a fast mission cannot trip flood control.
73
+ progress_msg_id: int | None = None
74
+ last_edit = [0.0]
75
+ if telegram is not None and topic_id is not None:
76
+ try:
77
+ progress_msg_id = telegram.post( # type: ignore[attr-defined]
78
+ topic_id, "Omega OS - mission starting...")
79
+ except Exception as exc: # noqa: BLE001
80
+ print(f"[mission] telegram post failed: {exc}")
81
+
82
+ def _live(event) -> None:
83
+ if event.type.value not in ("task.verified", "task.failed"):
84
+ return
85
+ if progress_msg_id is None:
86
+ return
87
+ now = time.time()
88
+ if now - last_edit[0] < _EDIT_THROTTLE_S:
89
+ return
90
+ last_edit[0] = now
91
+ missions = tracker.all()
92
+ if not missions:
93
+ return
94
+ try:
95
+ telegram.edit( # type: ignore[attr-defined]
96
+ progress_msg_id,
97
+ missions[0].render(title="Omega OS - mission in progress"))
98
+ except Exception: # noqa: BLE001 — progress is best-effort
99
+ pass
100
+
101
+ bus.subscribe(_live)
102
+
103
+ # the verification gate: the full Quality Arsenal when audit definitions are
104
+ # deployed (Agentik_SSOT/audits/), else the simple runtime gate.
105
+ router = build_router()
106
+ audits_dir = home / "Agentik_SSOT" / "audits"
107
+ if audits_dir.is_dir() and any(audits_dir.glob("*.yaml")):
108
+ from omega_engine.audit_arsenal import ArsenalGate, AuditRegistry
109
+ gate: object = ArsenalGate(AuditRegistry.load(audits_dir), router)
110
+ else:
111
+ gate = AuditGate()
112
+ executor = Executor(store, bus, router, gate)
113
+ result = executor.run_mission(intent)
114
+
115
+ # the Oracle ALWAYS produces a report
116
+ rdir = Path(report_dir or (home / "Agentik_Runtime" / "verdicts"))
117
+ report_pdf: Path | None
118
+ try:
119
+ report_pdf = generate_mission_report(result, store, rdir)
120
+ except Exception as exc: # noqa: BLE001
121
+ report_pdf = None
122
+ print(f"[mission] report generation failed: {exc}")
123
+
124
+ prog = tracker.progress(result.mission_id)
125
+ pct = prog.pct if prog else 0
126
+
127
+ if telegram is not None and topic_id is not None:
128
+ verdict = "VERIFIED" if result.verified else "PARTIAL / FAILED"
129
+ if progress_msg_id is not None and prog is not None:
130
+ try:
131
+ telegram.edit( # type: ignore[attr-defined]
132
+ progress_msg_id,
133
+ prog.render(title=f"{verdict} - mission complete"))
134
+ except Exception as exc: # noqa: BLE001
135
+ print(f"[mission] final progress edit failed: {exc}")
136
+ if report_pdf is not None:
137
+ try:
138
+ telegram.send_document( # type: ignore[attr-defined]
139
+ topic_id, report_pdf,
140
+ caption=f"{verdict} - mission report - {intent[:80]}")
141
+ except Exception as exc: # noqa: BLE001
142
+ print(f"[mission] report delivery failed (report is on disk): {exc}")
143
+
144
+ store.close()
145
+ return MissionOutcome(result=result, report_pdf=report_pdf, progress_pct=pct)
@@ -0,0 +1,75 @@
1
+ """Live progress tracking — turn the event stream into a progress bar.
2
+
3
+ `ProgressTracker` is an EventBus subscriber. It keeps one `MissionProgress` per
4
+ mission, derived purely from events — so progress, like completion, is never
5
+ self-declared.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+
11
+ from omega_engine.events import Event, EventType
12
+
13
+
14
+ @dataclass
15
+ class MissionProgress:
16
+ mission_id: str
17
+ total_tasks: int = 0
18
+ verified: int = 0
19
+ failed: int = 0
20
+ last_event: str = ""
21
+
22
+ @property
23
+ def done(self) -> int:
24
+ return self.verified + self.failed
25
+
26
+ @property
27
+ def pct(self) -> int:
28
+ return round(100 * self.done / self.total_tasks) if self.total_tasks else 0
29
+
30
+ def bar(self, width: int = 20) -> str:
31
+ filled = round(width * self.pct / 100)
32
+ return "█" * filled + "░" * (width - filled)
33
+
34
+ def render(self, title: str = "") -> str:
35
+ """A Telegram-ready progress block."""
36
+ head = f"{title}\n" if title else ""
37
+ return (
38
+ f"{head}{self.bar()} {self.pct}%\n"
39
+ f"{self.verified} verified · {self.failed} failed "
40
+ f"· {self.total_tasks} tasks\n"
41
+ f"last: {self.last_event}"
42
+ )
43
+
44
+
45
+ class ProgressTracker:
46
+ """Subscribe `.on_event` to an EventBus; read `.progress(mission_id)`."""
47
+
48
+ def __init__(self) -> None:
49
+ self._missions: dict[str, MissionProgress] = {}
50
+ self._task_mission: dict[str, str] = {}
51
+
52
+ def on_event(self, event: Event) -> None:
53
+ if event.type is EventType.CREATED:
54
+ mid = str(event.payload.get("mission_id", ""))
55
+ if mid:
56
+ self._task_mission[event.task_id] = mid
57
+ prog = self._missions.setdefault(mid, MissionProgress(mid))
58
+ prog.total_tasks += 1
59
+
60
+ mid = self._task_mission.get(event.task_id)
61
+ if not mid:
62
+ return
63
+ prog = self._missions[mid]
64
+ prog.last_event = f"{event.type.value} {event.task_id}"
65
+ if event.type is EventType.VERIFIED:
66
+ prog.verified += 1
67
+ elif event.type is EventType.FAILED:
68
+ prog.failed += 1
69
+
70
+ def progress(self, mission_id: str) -> MissionProgress | None:
71
+ return self._missions.get(mission_id)
72
+
73
+ def all(self) -> list[MissionProgress]:
74
+ """Every mission's progress — used by the live Telegram reporter."""
75
+ return list(self._missions.values())
@@ -0,0 +1,92 @@
1
+ """Project creation — structure, registry entry, and a bound Telegram topic.
2
+
3
+ Creating a project builds its folder under Agentik_Coding/projects/, registers
4
+ it in the canonical project registry (the project<->topic map that routes
5
+ Telegram messages), and — if a Telegram bridge is given — creates its forum
6
+ topic.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import re
13
+ import time
14
+ from dataclasses import asdict, dataclass
15
+ from pathlib import Path
16
+
17
+
18
+ def slugify(name: str) -> str:
19
+ slug = re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-")
20
+ return slug or "project"
21
+
22
+
23
+ @dataclass
24
+ class Project:
25
+ name: str
26
+ slug: str
27
+ path: str
28
+ topic_id: int | None = None
29
+ created_at: str = ""
30
+
31
+
32
+ def _omega_home(explicit: str | Path | None = None) -> Path:
33
+ return Path(explicit or os.environ.get("OMEGA_HOME", str(Path.home() / "Omega")))
34
+
35
+
36
+ def _registry_file(home: Path) -> Path:
37
+ return home / "Agentik_Coding" / "projects.json"
38
+
39
+
40
+ def load_projects(omega_home: str | Path | None = None) -> list[Project]:
41
+ """The canonical project registry — the project <-> Telegram-topic map."""
42
+ fpath = _registry_file(_omega_home(omega_home))
43
+ if not fpath_exists(fpath):
44
+ return []
45
+ rows = json.loads(fpath.read_text() or "[]")
46
+ return [Project(**row) for row in rows]
47
+
48
+
49
+ def fpath_exists(p: Path) -> bool:
50
+ return p.exists()
51
+
52
+
53
+ def _save(home: Path, projects: list[Project]) -> None:
54
+ fpath = _registry_file(home)
55
+ fpath.parent.mkdir(parents=True, exist_ok=True)
56
+ fpath.write_text(
57
+ json.dumps([asdict(p) for p in projects], indent=2, ensure_ascii=False)
58
+ )
59
+
60
+
61
+ def create_project(name: str, omega_home: str | Path | None = None,
62
+ telegram: object | None = None) -> Project:
63
+ """Create a project: folder + README + registry entry + Telegram topic.
64
+
65
+ `telegram` is an optional TelegramBridge — when given, a forum topic is
66
+ created and bound to the project.
67
+ """
68
+ home = _omega_home(omega_home)
69
+ slug = slugify(name)
70
+ projects = load_projects(home)
71
+ if any(p.slug == slug for p in projects):
72
+ raise ValueError(f"project '{slug}' already exists")
73
+
74
+ path = home / "Agentik_Coding" / "projects" / slug
75
+ path.mkdir(parents=True, exist_ok=True)
76
+ (path / "README.md").write_text(
77
+ f"# {name}\n\n"
78
+ f"Project managed by Omega OS. Missions addressed to this project "
79
+ f"route to its bound Telegram topic.\n"
80
+ )
81
+
82
+ topic_id: int | None = None
83
+ if telegram is not None:
84
+ topic_id = telegram.create_topic(name) # type: ignore[attr-defined]
85
+
86
+ project = Project(
87
+ name=name, slug=slug, path=str(path), topic_id=topic_id,
88
+ created_at=time.strftime("%Y-%m-%dT%H:%M:%S"),
89
+ )
90
+ projects.append(project)
91
+ _save(home, projects)
92
+ return project
@@ -0,0 +1,139 @@
1
+ """The provider abstraction — one contract, any LLM.
2
+
3
+ The executor talks only to `AgentProvider`. `MockProvider` makes the whole
4
+ orchestration testable with no network. `ClaudeProvider` is the real adapter.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import re
10
+ from dataclasses import dataclass, field
11
+ from typing import Any, Protocol, runtime_checkable
12
+
13
+
14
+ @dataclass
15
+ class AgentRequest:
16
+ role: str # "aisb" | "oracle" | "worker" | ...
17
+ prompt: str
18
+ context: dict[str, Any] = field(default_factory=dict)
19
+
20
+
21
+ @dataclass
22
+ class AgentResult:
23
+ text: str
24
+ claimed_done: bool = False
25
+ artifacts: dict[str, Any] = field(default_factory=dict)
26
+ plan: list[dict[str, Any]] = field(default_factory=list) # dispatcher roles only
27
+ usage: dict[str, int] = field(default_factory=dict)
28
+
29
+
30
+ @runtime_checkable
31
+ class AgentProvider(Protocol):
32
+ """Every LLM sits behind this. ~200-400 lines per real adapter."""
33
+
34
+ id: str
35
+
36
+ def run(self, req: AgentRequest) -> AgentResult: ...
37
+
38
+
39
+ class MockProvider:
40
+ """Deterministic provider — makes the executor fully testable with no API.
41
+
42
+ Dispatcher roles (oracle/manager) return a `plan`; workers return artifacts
43
+ and `claimed_done`; the verifier returns a passing score.
44
+ """
45
+
46
+ id = "mock"
47
+
48
+ def __init__(self, plan_size: int = 2) -> None:
49
+ self._plan_size = plan_size
50
+
51
+ def run(self, req: AgentRequest) -> AgentResult:
52
+ role = req.role
53
+ if role in ("oracle", "manager", "aisb"):
54
+ plan = [
55
+ {"role": "worker",
56
+ "spec": {"task": f"subtask {i + 1}", "verify_cmd": "true"}}
57
+ for i in range(self._plan_size)
58
+ ]
59
+ return AgentResult(text=f"planned {len(plan)} subtasks",
60
+ claimed_done=True, plan=plan)
61
+ if role == "worker":
62
+ task = req.context.get("task", "task")
63
+ return AgentResult(
64
+ text=f"completed: {task}",
65
+ claimed_done=True,
66
+ artifacts={"summary": f"completed: {task}", "files": ["mock_change.py"]},
67
+ usage={"input_tokens": 100, "output_tokens": 200},
68
+ )
69
+ if role in ("verifier", "audit"):
70
+ # a structured forensic verdict — what the Quality Arsenal expects
71
+ return AgentResult(
72
+ text="audit pass", claimed_done=True,
73
+ artifacts={"verdict": {
74
+ "score": 95, "verified": True, "confidence": "high",
75
+ "summary": "mock forensic audit — no falsifiable claims found",
76
+ "findings": [], "fix_plan": [],
77
+ }})
78
+ return AgentResult(text="ok", claimed_done=True)
79
+
80
+
81
+ class ClaudeProvider:
82
+ """Real provider — wraps the Anthropic SDK.
83
+
84
+ Needs `ANTHROPIC_API_KEY` in the environment (or passed) and the `anthropic`
85
+ package: `pip install omega-engine[providers]`. The credential resolves
86
+ through the Account pool (Agentik_Providers/claude/accounts.yaml).
87
+ """
88
+
89
+ id = "claude"
90
+
91
+ def __init__(self, model: str = "claude-opus-4-7", api_key: str | None = None) -> None:
92
+ self._model = model
93
+ self._api_key = api_key
94
+
95
+ def run(self, req: AgentRequest) -> AgentResult:
96
+ try:
97
+ import anthropic
98
+ except ImportError as exc: # pragma: no cover - depends on optional extra
99
+ raise RuntimeError(
100
+ "ClaudeProvider needs the `anthropic` package — "
101
+ "pip install omega-engine[providers]"
102
+ ) from exc
103
+
104
+ client = anthropic.Anthropic(api_key=self._api_key) # reads env if None
105
+ prompt = req.prompt
106
+ if req.role in ("oracle", "manager", "aisb"):
107
+ prompt += (
108
+ "\n\nRespond with a JSON array of subtasks, each "
109
+ '{"role":"worker","spec":{"task":"..."}}. JSON only.'
110
+ )
111
+ msg = client.messages.create(
112
+ model=self._model,
113
+ max_tokens=4096,
114
+ messages=[{"role": "user", "content": prompt}],
115
+ )
116
+ text = "".join(
117
+ b.text for b in msg.content if getattr(b, "type", "") == "text"
118
+ )
119
+ return AgentResult(
120
+ text=text,
121
+ claimed_done=True,
122
+ plan=_extract_plan(text) if req.role in ("oracle", "manager", "aisb") else [],
123
+ usage={
124
+ "input_tokens": msg.usage.input_tokens,
125
+ "output_tokens": msg.usage.output_tokens,
126
+ },
127
+ )
128
+
129
+
130
+ def _extract_plan(text: str) -> list[dict[str, Any]]:
131
+ """Pull a JSON subtask array out of an LLM response. Empty list if none."""
132
+ match = re.search(r"\[.*\]", text, re.DOTALL)
133
+ if not match:
134
+ return []
135
+ try:
136
+ data = json.loads(match.group(0))
137
+ return data if isinstance(data, list) else []
138
+ except json.JSONDecodeError:
139
+ return []