Import ducklm runtime
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
"""Event bus and event store."""
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
|
||||
from app.core.contracts import RuntimeEvent
|
||||
from app.events.event_store import SQLiteEventStore
|
||||
|
||||
|
||||
Subscriber = Callable[[RuntimeEvent], None]
|
||||
|
||||
|
||||
class EventBus:
|
||||
"""Per-task ordered event publishing with durable storage."""
|
||||
|
||||
def __init__(self, event_store: SQLiteEventStore) -> None:
|
||||
self._store = event_store
|
||||
self._subscribers: list[Subscriber] = []
|
||||
|
||||
def next_sequence(self, task_id: str) -> int:
|
||||
return self._store.get_latest_sequence(task_id) + 1
|
||||
|
||||
def publish(self, event: RuntimeEvent) -> RuntimeEvent:
|
||||
self._store.append(event)
|
||||
for subscriber in self._subscribers:
|
||||
subscriber(event)
|
||||
return event
|
||||
|
||||
def subscribe(self, subscriber: Subscriber) -> None:
|
||||
self._subscribers.append(subscriber)
|
||||
|
||||
def list_for_task(self, task_id: str) -> list[RuntimeEvent]:
|
||||
return self._store.list_for_task(task_id)
|
||||
@@ -0,0 +1,94 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
from app.core.contracts import RuntimeEvent
|
||||
|
||||
|
||||
class SQLiteEventStore:
|
||||
"""Append-only event store with per-task ordered history."""
|
||||
|
||||
def __init__(self, db_path: str | Path) -> None:
|
||||
self._db_path = Path(db_path)
|
||||
self._db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self._initialize()
|
||||
|
||||
def append(self, event: RuntimeEvent) -> None:
|
||||
with sqlite3.connect(self._db_path) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO events (
|
||||
event_id, task_id, session_id, sequence, type, timestamp,
|
||||
payload_json, causation_id, correlation_id
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
event.event_id,
|
||||
event.task_id,
|
||||
event.session_id,
|
||||
event.sequence,
|
||||
event.type,
|
||||
event.timestamp.isoformat(),
|
||||
json.dumps(event.payload),
|
||||
event.causation_id,
|
||||
event.correlation_id,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def list_for_task(self, task_id: str) -> list[RuntimeEvent]:
|
||||
with sqlite3.connect(self._db_path) as conn:
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT event_id, task_id, session_id, sequence, type, timestamp,
|
||||
payload_json, causation_id, correlation_id
|
||||
FROM events
|
||||
WHERE task_id = ?
|
||||
ORDER BY sequence ASC
|
||||
""",
|
||||
(task_id,),
|
||||
).fetchall()
|
||||
return [
|
||||
RuntimeEvent(
|
||||
event_id=row[0],
|
||||
task_id=row[1],
|
||||
session_id=row[2],
|
||||
sequence=row[3],
|
||||
type=row[4],
|
||||
timestamp=row[5],
|
||||
payload=json.loads(row[6]),
|
||||
causation_id=row[7],
|
||||
correlation_id=row[8],
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def get_latest_sequence(self, task_id: str) -> int:
|
||||
with sqlite3.connect(self._db_path) as conn:
|
||||
row = conn.execute(
|
||||
"SELECT COALESCE(MAX(sequence), 0) FROM events WHERE task_id = ?",
|
||||
(task_id,),
|
||||
).fetchone()
|
||||
return int(row[0]) if row else 0
|
||||
|
||||
def _initialize(self) -> None:
|
||||
with sqlite3.connect(self._db_path) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
event_id TEXT PRIMARY KEY,
|
||||
task_id TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
sequence INTEGER NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL,
|
||||
payload_json TEXT NOT NULL,
|
||||
causation_id TEXT,
|
||||
correlation_id TEXT NOT NULL,
|
||||
UNIQUE(task_id, sequence)
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
@@ -0,0 +1,31 @@
|
||||
TASK_RECEIVED = "task_received"
|
||||
CONTEXT_BUILT = "context_built"
|
||||
STEP_STARTED = "step_started"
|
||||
TOOL_CALLED = "tool_called"
|
||||
TOOL_COMPLETED = "tool_completed"
|
||||
PERMISSION_REQUESTED = "permission_requested"
|
||||
PERMISSION_RESOLVED = "permission_resolved"
|
||||
TASK_AWAITING_PERMISSION = "task_awaiting_permission"
|
||||
SECRET_REQUESTED = "secret_requested"
|
||||
TASK_AWAITING_INPUT = "task_awaiting_input"
|
||||
CHECKPOINT_SAVED = "checkpoint_saved"
|
||||
TASK_COMPLETED = "task_completed"
|
||||
TASK_FAILED = "task_failed"
|
||||
ORCHESTRATOR_CALLED = "orchestrator_called"
|
||||
ORCHESTRATOR_RESULT = "orchestrator_result"
|
||||
ORCHESTRATOR_UNAVAILABLE = "orchestrator_unavailable"
|
||||
ORCHESTRATOR_FALLBACK_USED = "orchestrator_fallback_used"
|
||||
ORCHESTRATOR_RETRY = "orchestrator_retry"
|
||||
PLANNER_CALLED = "planner_called"
|
||||
PLANNER_RETRY = "planner_retry"
|
||||
CRITIC_CALLED = "critic_called"
|
||||
CRITIC_RESULT = "critic_result"
|
||||
MEMORY_WRITE_DECIDED = "memory_write_decided"
|
||||
PLAN_STARTED = "plan_started"
|
||||
PLAN_FAILED = "plan_failed"
|
||||
PLAN_COMPLETED = "plan_completed"
|
||||
STEPPED_COMPLETED = "step_completed"
|
||||
THINKER_CALLED = "thinker_called"
|
||||
THINKER_RESULT = "thinker_result"
|
||||
JSON_COMPILER_CALLED = "json_compiler_called"
|
||||
JSON_COMPILER_RESULT = "json_compiler_result"
|
||||
Reference in New Issue
Block a user