@oswaldzsh/devhive 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 (46) hide show
  1. package/README.md +91 -0
  2. package/__init__.py +0 -0
  3. package/agents/__init__.py +0 -0
  4. package/agents/base.py +118 -0
  5. package/agents/execute.py +150 -0
  6. package/agents/verifier_dynamic.py +164 -0
  7. package/agents/verifier_semantic.py +84 -0
  8. package/agents/verifier_static.py +153 -0
  9. package/bin/dh +77 -0
  10. package/config.yaml +71 -0
  11. package/control_plane/__init__.py +0 -0
  12. package/control_plane/cli.py +596 -0
  13. package/control_plane/dashboard.py +57 -0
  14. package/control_plane/notifications.py +54 -0
  15. package/control_plane/tui.py +352 -0
  16. package/install.sh +67 -0
  17. package/orchestrator/__init__.py +0 -0
  18. package/orchestrator/agent_pool.py +107 -0
  19. package/orchestrator/convergence_gate.py +133 -0
  20. package/orchestrator/engine.py +353 -0
  21. package/orchestrator/event_bus.py +58 -0
  22. package/orchestrator/task_queue.py +59 -0
  23. package/package.json +50 -0
  24. package/protocol/__init__.py +0 -0
  25. package/protocol/schemas.py +222 -0
  26. package/setup.py +44 -0
  27. package/signature/__init__.py +0 -0
  28. package/signature/engine.py +211 -0
  29. package/signature/extractor.py +156 -0
  30. package/signature/learner.py +75 -0
  31. package/signature/src/matcher.c +263 -0
  32. package/signature/src/matcher.h +135 -0
  33. package/signatures/seed_signatures.json +174 -0
  34. package/storage/__init__.py +0 -0
  35. package/storage/checkpoint.py +153 -0
  36. package/storage/signature_db.py +62 -0
  37. package/tools/__init__.py +0 -0
  38. package/tools/api_client.py +101 -0
  39. package/tools/git.py +75 -0
  40. package/tools/sandbox.py +79 -0
  41. package/verification/__init__.py +0 -0
  42. package/verification/diagnostic.py +124 -0
  43. package/verification/patterns/api_breaking.yaml +25 -0
  44. package/verification/patterns/code_quality.yaml +41 -0
  45. package/verification/patterns/security.yaml +41 -0
  46. package/verification/pipeline.py +61 -0
@@ -0,0 +1,133 @@
1
+ """Convergence Gate — determines whether to pass, retry, or escalate."""
2
+
3
+ import hashlib
4
+ import json
5
+ from collections import deque
6
+ from dataclasses import dataclass, field
7
+ from typing import Optional
8
+
9
+ from protocol.schemas import (
10
+ Task, ExecutionHandoff, Verdict, SemanticVerdict, ConvergenceDecision,
11
+ ConcurrencyAction, EscalationReport,
12
+ )
13
+ from verification.diagnostic import DiagnosticAggregator, AggregatorResult
14
+
15
+
16
+ @dataclass
17
+ class StageState:
18
+ stage: str
19
+ attempt: int
20
+ fingerprint: str
21
+
22
+
23
+ class ConvergenceGate:
24
+ """Implements L1/L2 convergence checks with escalation rules."""
25
+
26
+ def __init__(self, config: dict = None):
27
+ self.config = config or {}
28
+ self.max_l1_retries = config.get("l1_max_retries", 3)
29
+ self.max_l2_retries = config.get("l2_max_retries", 2)
30
+ self.loop_window = config.get("loop_detection_window", 5)
31
+ self.loop_threshold = config.get("loop_similarity_threshold", 0.8)
32
+ self.aggregator = DiagnosticAggregator(config)
33
+ self._stage_history: dict[str, deque[StageState]] = {}
34
+
35
+ def evaluate_l1(self, task: Task, static: Verdict, dynamic: Verdict,
36
+ attempt: int) -> ConvergenceDecision:
37
+ """Evaluate L1 convergence."""
38
+ result = self.aggregator.aggregate_l1(static, dynamic, task.id)
39
+
40
+ # Check retry limit
41
+ if result.action == ConcurrencyAction.FIX and attempt >= self.max_l1_retries:
42
+ return ConvergenceDecision(
43
+ action=ConcurrencyAction.ESCALATE,
44
+ reason=f"L1 retry limit ({self.max_l1_retries}) exceeded",
45
+ escalation=self._build_escalation(task, result),
46
+ )
47
+
48
+ # Check for loop detection
49
+ if self._detect_loop(task.id, "L1"):
50
+ return ConvergenceDecision(
51
+ action=ConcurrencyAction.ESCALATE,
52
+ reason="Loop detected in L1 verification",
53
+ escalation=self._build_escalation(task, result),
54
+ )
55
+
56
+ return ConvergenceDecision(
57
+ action=result.action,
58
+ reason=result.reason,
59
+ fix_strategy=result.fix_strategy,
60
+ )
61
+
62
+ def evaluate_l2(self, task: Task, static: Verdict, dynamic: Verdict,
63
+ semantic: SemanticVerdict, mutation: Optional[Verdict],
64
+ attempt: int) -> ConvergenceDecision:
65
+ """Evaluate L2 convergence."""
66
+ result = self.aggregator.aggregate_l2(
67
+ static, dynamic, semantic, mutation, task.id
68
+ )
69
+
70
+ if result.action == ConcurrencyAction.FIX and attempt >= self.max_l2_retries:
71
+ return ConvergenceDecision(
72
+ action=ConcurrencyAction.ESCALATE,
73
+ reason=f"L2 retry limit ({self.max_l2_retries}) exceeded",
74
+ escalation=self._build_escalation(task, result),
75
+ )
76
+
77
+ return ConvergenceDecision(
78
+ action=result.action,
79
+ reason=result.reason,
80
+ fix_strategy=result.fix_strategy,
81
+ )
82
+
83
+ def check_sensitive_modules(self, task: Task) -> bool:
84
+ """Check if the task touches modules that require human approval."""
85
+ sensitive = set(self.config.get("escalation", {}).get(
86
+ "require_approval_for", []))
87
+ task_modules = set(task.spec.sensitive_modules)
88
+ return bool(sensitive & task_modules)
89
+
90
+ def _detect_loop(self, task_id: str, stage: str) -> bool:
91
+ """Check if recent stage fingerprints indicate a loop."""
92
+ if task_id not in self._stage_history:
93
+ self._stage_history[task_id] = deque(maxlen=self.loop_window)
94
+ return False
95
+
96
+ history = self._stage_history[task_id]
97
+ if len(history) < self.loop_window:
98
+ return False
99
+
100
+ # Count similar fingerprints in the window
101
+ fingerprints = [h.fingerprint for h in history]
102
+ unique = len(set(fingerprints))
103
+ similarity = 1.0 - (unique / len(fingerprints))
104
+ return similarity >= self.loop_threshold
105
+
106
+ def record_state(self, task_id: str, stage: str, attempt: int,
107
+ handoff: dict = None, verdict: dict = None):
108
+ """Record stage state for loop detection."""
109
+ if task_id not in self._stage_history:
110
+ self._stage_history[task_id] = deque(maxlen=self.loop_window)
111
+
112
+ # Build a fingerprint
113
+ data = json.dumps({
114
+ "stage": stage,
115
+ "handoff_keys": sorted(handoff.keys()) if handoff else [],
116
+ "verdict_overall": verdict.get("overall") if verdict else None,
117
+ }, sort_keys=True)
118
+ fingerprint = hashlib.sha256(data.encode()).hexdigest()[:16]
119
+
120
+ self._stage_history[task_id].append(StageState(
121
+ stage=stage, attempt=attempt, fingerprint=fingerprint,
122
+ ))
123
+
124
+ def _build_escalation(self, task: Task,
125
+ result: AggregatorResult) -> EscalationReport:
126
+ from datetime import datetime, timezone
127
+ return EscalationReport(
128
+ escalation_id=f"esc-{task.id}-{datetime.now(timezone.utc).timestamp()}",
129
+ task_id=task.id,
130
+ triggered_by=result.reason,
131
+ current_state={"stage": task.current_stage},
132
+ suggested_human_action=result.reason,
133
+ )
@@ -0,0 +1,353 @@
1
+ """Orchestrator Engine — the central event loop for DevHive."""
2
+
3
+ import asyncio
4
+ import json
5
+ import time
6
+ from datetime import datetime, timezone
7
+ from typing import Optional
8
+
9
+ from protocol.schemas import (
10
+ Task, ExecutionHandoff, Verdict, SemanticVerdict, DevHiveEvent,
11
+ VerdictOverall, ConcurrencyAction, TaskSpec, Priority,
12
+ )
13
+ from orchestrator.event_bus import EventBus
14
+ from orchestrator.task_queue import TaskQueue
15
+ from orchestrator.agent_pool import AgentPool, AgentType
16
+ from orchestrator.convergence_gate import ConvergenceGate
17
+ from verification.pipeline import VerificationPipeline
18
+ from verification.diagnostic import DiagnosticAggregator
19
+ from storage.checkpoint import CheckpointStore
20
+
21
+
22
+ class Orchestrator:
23
+ """Central orchestrator for the DevHive multi-agent system.
24
+
25
+ Routes events between agents, manages task lifecycle,
26
+ and enforces convergence gates.
27
+ """
28
+
29
+ def __init__(self, config: dict = None):
30
+ self.config = config or {}
31
+ self.event_bus = EventBus()
32
+ self.task_queue = TaskQueue()
33
+ self.agent_pool = AgentPool(config)
34
+ self.verification = VerificationPipeline(config.get("verification", {}))
35
+ self.convergence = ConvergenceGate(config.get("convergence", {}))
36
+ self.checkpoint = CheckpointStore(config.get("checkpoint_db",
37
+ "storage/devhive.db"))
38
+ self.diagnostic = DiagnosticAggregator(config)
39
+ self._task_attempts: dict[str, dict[str, int]] = {}
40
+
41
+ async def start(self, agent_counts: dict[str, int] = None):
42
+ """Start the orchestrator and all agent processes."""
43
+ counts = agent_counts or {
44
+ "execute": 1,
45
+ "static_verifier": 1,
46
+ "dynamic_verifier": 1,
47
+ "semantic_verifier": 1,
48
+ }
49
+
50
+ # Start agents
51
+ agent_type_map = {
52
+ "execute": AgentType.EXECUTE,
53
+ "static_verifier": AgentType.STATIC_VERIFIER,
54
+ "dynamic_verifier": AgentType.DYNAMIC_VERIFIER,
55
+ "semantic_verifier": AgentType.SEMANTIC_VERIFIER,
56
+ }
57
+
58
+ for name, count in counts.items():
59
+ atype = agent_type_map.get(name)
60
+ if atype:
61
+ for _ in range(count):
62
+ self.agent_pool.start_agent(atype)
63
+
64
+ # Subscribe to events
65
+ self.event_bus.subscribe("task.created", self._on_task_created)
66
+ self.event_bus.subscribe("agent.idle", self._on_agent_idle)
67
+ self.event_bus.subscribe("handoff.emitted", self._on_handoff)
68
+ self.event_bus.subscribe("verdict.ready", self._on_verdict)
69
+ self.event_bus.subscribe("escalation.needed", self._on_escalation)
70
+
71
+ # Start event processing
72
+ await self.event_bus.start()
73
+
74
+ # Start result collection loop
75
+ asyncio.create_task(self._collect_results())
76
+
77
+ async def stop(self):
78
+ """Stop the orchestrator and all agents."""
79
+ self.agent_pool.stop_all()
80
+ await self.event_bus.stop()
81
+
82
+ async def submit_task(self, spec: TaskSpec, branch: str = "main",
83
+ base_commit: str = "HEAD") -> str:
84
+ """Submit a new task to the system."""
85
+ import uuid
86
+ task_id = f"task-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}-{uuid.uuid4().hex[:6]}"
87
+
88
+ task = Task(
89
+ id=task_id,
90
+ spec=spec,
91
+ branch=branch,
92
+ base_commit=base_commit,
93
+ )
94
+
95
+ # Persist
96
+ self.checkpoint.save_task(task_id, json.dumps(spec.model_dump()),
97
+ branch, base_commit)
98
+
99
+ # Enqueue
100
+ self.task_queue.enqueue(task)
101
+
102
+ # Publish event
103
+ await self.event_bus.publish(DevHiveEvent(
104
+ event_type="task.created",
105
+ task_id=task_id,
106
+ payload={"task": task.model_dump()},
107
+ ))
108
+
109
+ return task_id
110
+
111
+ async def _on_task_created(self, event: DevHiveEvent):
112
+ """A new task was created — try to dispatch it."""
113
+ task = self.task_queue.peek(event.task_id)
114
+ if task:
115
+ await self._try_dispatch(task)
116
+
117
+ async def _on_agent_idle(self, event: DevHiveEvent):
118
+ """An agent became idle — try to dispatch pending tasks."""
119
+ agent_type_str = event.payload.get("agent_type", "")
120
+ agent_map = {
121
+ "execute": AgentType.EXECUTE,
122
+ "static_verifier": AgentType.STATIC_VERIFIER,
123
+ "dynamic_verifier": AgentType.DYNAMIC_VERIFIER,
124
+ "semantic_verifier": AgentType.SEMANTIC_VERIFIER,
125
+ }
126
+ atype = agent_map.get(agent_type_str)
127
+ if atype:
128
+ task = self.task_queue.next_pending(agent_type_str)
129
+ if task:
130
+ handle = self.agent_pool.get_idle(atype)
131
+ if handle:
132
+ self.agent_pool.dispatch(handle, task)
133
+ self._increment_attempt(task.id, "execute")
134
+ self.checkpoint.update_task_stage(task.id, "EXECUTE")
135
+
136
+ async def _on_handoff(self, event: DevHiveEvent):
137
+ """Execute Agent produced a Handoff → trigger L1 verification."""
138
+ task_id = event.task_id
139
+ handoff_data = event.payload.get("handoff", {})
140
+ task = self.task_queue.peek(task_id)
141
+
142
+ if not task:
143
+ return
144
+
145
+ # Save checkpoint
146
+ self.checkpoint.save_checkpoint(
147
+ checkpoint_id=f"{task_id}-execute",
148
+ task_id=task_id,
149
+ stage="EXECUTE",
150
+ agent_id=event.payload.get("agent_id", ""),
151
+ handoff_json=json.dumps(handoff_data),
152
+ outcome="COMPLETED",
153
+ )
154
+ self.checkpoint.update_task_stage(task_id, "VERIFY_L1")
155
+
156
+ # Run L1 verification
157
+ static, dynamic = await self.verification.run_l1(task)
158
+
159
+ # Evaluate convergence
160
+ attempt = self._get_attempt(task_id, "l1")
161
+ decision = self.convergence.evaluate_l1(task, static, dynamic, attempt)
162
+
163
+ # Save checkpoints
164
+ self.checkpoint.save_checkpoint(
165
+ checkpoint_id=f"{task_id}-static",
166
+ task_id=task_id, stage="VERIFY_L1",
167
+ agent_id="static-verifier",
168
+ verdict_json=json.dumps(static.model_dump()),
169
+ outcome=static.overall.value,
170
+ )
171
+ self.checkpoint.save_checkpoint(
172
+ checkpoint_id=f"{task_id}-dynamic",
173
+ task_id=task_id, stage="VERIFY_L1",
174
+ agent_id="dynamic-verifier",
175
+ verdict_json=json.dumps(dynamic.model_dump()),
176
+ outcome=dynamic.overall.value,
177
+ )
178
+
179
+ # Act on decision
180
+ await self._handle_convergence(task_id, decision)
181
+
182
+ async def _on_verdict(self, event: DevHiveEvent):
183
+ """Handle manual verdict submission."""
184
+ pass # Reserved for human-in-the-loop verdict input
185
+
186
+ async def _on_escalation(self, event: DevHiveEvent):
187
+ """An escalation was triggered — notify human operators."""
188
+ task_id = event.task_id
189
+ report = event.payload.get("report", {})
190
+ self.checkpoint.save_escalation(
191
+ escalation_id=report.get("escalation_id", f"esc-{task_id}"),
192
+ task_id=task_id,
193
+ report_json=json.dumps(report),
194
+ )
195
+ self._notify_human(task_id, report)
196
+
197
+ async def _handle_convergence(self, task_id: str,
198
+ decision) -> None:
199
+ """Process a convergence decision."""
200
+ task = self.task_queue.peek(task_id)
201
+
202
+ if decision.action == ConcurrencyAction.PASS:
203
+ # L1 passed → run L2
204
+ self._increment_attempt(task_id, "l2")
205
+ self.checkpoint.update_task_stage(task_id, "VERIFY_L2")
206
+ semantic = await self.verification.run_l2(task)
207
+ mutation = await self.verification.run_mutation(task)
208
+
209
+ l2_decision = self.convergence.evaluate_l2(
210
+ task, Verdict(verifier_type="static", task_id=task_id,
211
+ overall=VerdictOverall.PASS, findings=[]),
212
+ Verdict(verifier_type="dynamic", task_id=task_id,
213
+ overall=VerdictOverall.PASS, findings=[]),
214
+ semantic, mutation,
215
+ self._get_attempt(task_id, "l2"),
216
+ )
217
+
218
+ self.checkpoint.save_checkpoint(
219
+ checkpoint_id=f"{task_id}-semantic",
220
+ task_id=task_id, stage="VERIFY_L2",
221
+ agent_id="semantic-verifier",
222
+ verdict_json=json.dumps(semantic.model_dump()),
223
+ outcome=semantic.alignment.value,
224
+ )
225
+
226
+ if l2_decision.action == ConcurrencyAction.PASS:
227
+ self.checkpoint.update_task_stage(task_id, "MERGE")
228
+ await self.event_bus.publish(DevHiveEvent(
229
+ event_type="convergence.reached",
230
+ task_id=task_id,
231
+ payload={"status": "ready_to_merge"},
232
+ ))
233
+ else:
234
+ await self._escalate(task_id, l2_decision)
235
+
236
+ elif decision.action == ConcurrencyAction.FIX:
237
+ # Retry with fix strategy
238
+ await self._retry_task(task_id, decision.fix_strategy)
239
+
240
+ elif decision.action == ConcurrencyAction.ESCALATE:
241
+ await self._escalate(task_id, decision)
242
+
243
+ elif decision.action == ConcurrencyAction.CONFLICT:
244
+ await self._escalate(task_id, decision)
245
+
246
+ async def _retry_task(self, task_id: str, fix_strategy: str = None):
247
+ """Retry execution with a fix strategy."""
248
+ task = self.task_queue.peek(task_id)
249
+ if task:
250
+ self.task_queue.enqueue(task)
251
+ await self._try_dispatch(task)
252
+
253
+ async def _escalate(self, task_id: str, decision) -> None:
254
+ """Escalate to human operators."""
255
+ escalation = decision.escalation or {
256
+ "escalation_id": f"esc-{task_id}",
257
+ "task_id": task_id,
258
+ "triggered_by": decision.reason,
259
+ }
260
+
261
+ await self.event_bus.publish(DevHiveEvent(
262
+ event_type="escalation.needed",
263
+ task_id=task_id,
264
+ payload={"report": escalation},
265
+ ))
266
+
267
+ async def _try_dispatch(self, task: Task):
268
+ """Try to dispatch a task to an idle Execute Agent."""
269
+ handle = self.agent_pool.get_idle(AgentType.EXECUTE)
270
+ if handle:
271
+ self.agent_pool.dispatch(handle, task)
272
+ self._increment_attempt(task.id, "execute")
273
+ self.checkpoint.update_task_stage(task.id, "EXECUTE")
274
+
275
+ async def _collect_results(self):
276
+ """Background task: collect results from agent processes."""
277
+ while True:
278
+ await asyncio.sleep(0.5)
279
+ for agent_id, handle in list(self.agent_pool.agents.items()):
280
+ try:
281
+ while not handle.result_queue.empty():
282
+ result = handle.result_queue.get_nowait()
283
+ await self._process_agent_result(agent_id, result)
284
+ except Exception:
285
+ pass
286
+
287
+ async def _process_agent_result(self, agent_id: str, result: dict):
288
+ """Process a result from an agent process."""
289
+ result_type = result.get("type", "")
290
+
291
+ if result_type == "agent.idle":
292
+ self.agent_pool.mark_idle(agent_id)
293
+ await self.event_bus.publish(DevHiveEvent(
294
+ event_type="agent.idle",
295
+ task_id="",
296
+ payload={"agent_id": agent_id,
297
+ "agent_type": result.get("agent_type", "")},
298
+ ))
299
+
300
+ elif result_type == "success":
301
+ task_id = result.get("task_id", "")
302
+ self.agent_pool.mark_idle(agent_id)
303
+
304
+ if "handoff" in result.get("result", {}):
305
+ # Execute agent produced a handoff
306
+ await self.event_bus.publish(DevHiveEvent(
307
+ event_type="handoff.emitted",
308
+ task_id=task_id,
309
+ payload={"handoff": result["result"]["handoff"],
310
+ "agent_id": agent_id},
311
+ ))
312
+
313
+ elif result_type == "stuck":
314
+ task_id = result.get("task_id", "")
315
+ self.agent_pool.mark_idle(agent_id)
316
+ await self.event_bus.publish(DevHiveEvent(
317
+ event_type="escalation.needed",
318
+ task_id=task_id,
319
+ payload={"reason": "agent_stuck",
320
+ "error": result.get("error", ""),
321
+ "agent_id": agent_id},
322
+ ))
323
+
324
+ elif result_type == "error":
325
+ task_id = result.get("task_id", "")
326
+ self.agent_pool.mark_idle(agent_id)
327
+ await self.event_bus.publish(DevHiveEvent(
328
+ event_type="escalation.needed",
329
+ task_id=task_id,
330
+ payload={"reason": "agent_error",
331
+ "error": result.get("error", ""),
332
+ "agent_id": agent_id},
333
+ ))
334
+
335
+ def _increment_attempt(self, task_id: str, stage: str):
336
+ if task_id not in self._task_attempts:
337
+ self._task_attempts[task_id] = {}
338
+ self._task_attempts[task_id][stage] = \
339
+ self._task_attempts[task_id].get(stage, 0) + 1
340
+
341
+ def _get_attempt(self, task_id: str, stage: str) -> int:
342
+ return self._task_attempts.get(task_id, {}).get(stage, 1)
343
+
344
+ def _notify_human(self, task_id: str, report: dict):
345
+ """Send desktop notification for escalations."""
346
+ msg = f"[DevHive] Task {task_id} needs attention\n{report.get('triggered_by', '')}"
347
+ try:
348
+ import subprocess
349
+ subprocess.run(["notify-send", "DevHive Escalation", msg],
350
+ timeout=5)
351
+ except Exception:
352
+ pass # notify-send not available
353
+ print(f"\n{'='*60}\nESCALATION: {task_id}\n{msg}\n{'='*60}\n")
@@ -0,0 +1,58 @@
1
+ """Event Bus — async publish/subscribe for agent communication."""
2
+
3
+ import asyncio
4
+ from collections import defaultdict
5
+ from typing import Callable, Awaitable
6
+
7
+ from protocol.schemas import DevHiveEvent
8
+
9
+
10
+ EventHandler = Callable[[DevHiveEvent], Awaitable[None]]
11
+
12
+
13
+ class EventBus:
14
+ """Lightweight pub/sub event bus for orchestrator-agent communication."""
15
+
16
+ def __init__(self):
17
+ self._subscribers: dict[str, list[EventHandler]] = defaultdict(list)
18
+ self._queue: asyncio.Queue[DevHiveEvent] = asyncio.Queue()
19
+ self._running = False
20
+ self._task: asyncio.Task | None = None
21
+
22
+ def subscribe(self, event_type: str, handler: EventHandler):
23
+ """Register a handler for a specific event type."""
24
+ self._subscribers[event_type].append(handler)
25
+
26
+ async def publish(self, event: DevHiveEvent):
27
+ """Publish an event to the bus. Non-blocking."""
28
+ await self._queue.put(event)
29
+
30
+ async def start(self):
31
+ """Start the event processing loop."""
32
+ self._running = True
33
+ self._task = asyncio.create_task(self._process_events())
34
+
35
+ async def stop(self):
36
+ """Stop the event processing loop."""
37
+ self._running = False
38
+ if self._task:
39
+ self._task.cancel()
40
+ try:
41
+ await self._task
42
+ except asyncio.CancelledError:
43
+ pass
44
+
45
+ async def _process_events(self):
46
+ """Main event processing loop."""
47
+ while self._running:
48
+ try:
49
+ event = await asyncio.wait_for(self._queue.get(), timeout=1.0)
50
+ handlers = self._subscribers.get(event.event_type, [])
51
+ # Dispatch to all handlers concurrently
52
+ tasks = [handler(event) for handler in handlers]
53
+ if tasks:
54
+ await asyncio.gather(*tasks, return_exceptions=True)
55
+ except asyncio.TimeoutError:
56
+ continue
57
+ except Exception:
58
+ continue
@@ -0,0 +1,59 @@
1
+ """Task Queue — priority-based task scheduling."""
2
+
3
+ from collections import deque
4
+ from dataclasses import dataclass, field
5
+ from typing import Optional
6
+
7
+ from protocol.schemas import Task, Priority
8
+
9
+
10
+ @dataclass
11
+ class QueueEntry:
12
+ task: Task
13
+ enqueued_at: float
14
+ priority: Priority = Priority.MEDIUM
15
+
16
+
17
+ class TaskQueue:
18
+ """Simple priority-based task queue with FIFO within each priority level."""
19
+
20
+ def __init__(self):
21
+ self._queues: dict[Priority, deque[QueueEntry]] = {
22
+ Priority.CRITICAL: deque(),
23
+ Priority.HIGH: deque(),
24
+ Priority.MEDIUM: deque(),
25
+ Priority.LOW: deque(),
26
+ }
27
+ self._all_tasks: dict[str, Task] = {}
28
+
29
+ def enqueue(self, task: Task) -> QueueEntry:
30
+ import time
31
+ entry = QueueEntry(task=task, enqueued_at=time.monotonic(),
32
+ priority=task.spec.priority)
33
+ self._queues[task.spec.priority].append(entry)
34
+ self._all_tasks[task.id] = task
35
+ return entry
36
+
37
+ def next_pending(self, agent_type: str = None) -> Optional[Task]:
38
+ """Get the next pending task by priority."""
39
+ for priority in (Priority.CRITICAL, Priority.HIGH,
40
+ Priority.MEDIUM, Priority.LOW):
41
+ if self._queues[priority]:
42
+ entry = self._queues[priority].popleft()
43
+ return entry.task
44
+ return None
45
+
46
+ def peek(self, task_id: str) -> Optional[Task]:
47
+ return self._all_tasks.get(task_id)
48
+
49
+ def pending_count(self) -> int:
50
+ return sum(len(q) for q in self._queues.values())
51
+
52
+ def remove(self, task_id: str):
53
+ """Remove a task from the queue."""
54
+ for q in self._queues.values():
55
+ for i, entry in enumerate(q):
56
+ if entry.task.id == task_id:
57
+ del q[i]
58
+ break
59
+ self._all_tasks.pop(task_id, None)
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@oswaldzsh/devhive",
3
+ "version": "0.1.0",
4
+ "description": "Multi-Agent Software Development System — autonomous coding with verify-specialized agents, failure signatures, and structured handoff protocols.",
5
+ "keywords": [
6
+ "ai",
7
+ "agent",
8
+ "coding",
9
+ "devtool",
10
+ "multi-agent",
11
+ "claude",
12
+ "orchestration",
13
+ "verification",
14
+ "autonomous"
15
+ ],
16
+ "homepage": "https://github.com/lejurobot/devhive",
17
+ "license": "MIT",
18
+ "bin": {
19
+ "dh": "bin/dh"
20
+ },
21
+ "files": [
22
+ "bin/",
23
+ "control_plane/",
24
+ "agents/",
25
+ "orchestrator/",
26
+ "protocol/",
27
+ "verification/",
28
+ "signature/",
29
+ "storage/",
30
+ "tools/",
31
+ "signatures/",
32
+ "config.yaml",
33
+ "setup.py",
34
+ "install.sh",
35
+ "__init__.py"
36
+ ],
37
+ "scripts": {
38
+ "postinstall": "bash install.sh || echo 'DevHive: Python deps not installed. Run: pip3 install httpx pydantic pyyaml rich'",
39
+ "uninstall": "rm -rf ~/.devhive"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/lejurobot/devhive"
44
+ },
45
+ "engines": {
46
+ "node": ">=16.0.0"
47
+ },
48
+ "os": ["darwin", "linux"],
49
+ "preferGlobal": true
50
+ }
File without changes