agent-runtime-core 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,167 @@
1
+ """
2
+ Abstract base class for run queue implementations.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime, timezone
8
+ from typing import Optional
9
+ from uuid import UUID
10
+
11
+
12
+ @dataclass
13
+ class QueuedRun:
14
+ """A run waiting in the queue."""
15
+
16
+ run_id: UUID
17
+ agent_key: str
18
+ input: dict
19
+ metadata: dict = field(default_factory=dict)
20
+ priority: int = 0
21
+ attempt: int = 1
22
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
23
+ scheduled_at: Optional[datetime] = None
24
+
25
+
26
+ class RunQueue(ABC):
27
+ """
28
+ Abstract interface for run queue implementations.
29
+
30
+ Queues handle:
31
+ - Enqueueing new runs
32
+ - Claiming runs for processing
33
+ - Lease management
34
+ - Retries
35
+ """
36
+
37
+ @abstractmethod
38
+ async def enqueue(
39
+ self,
40
+ run_id: UUID,
41
+ agent_key: str,
42
+ input: dict,
43
+ metadata: Optional[dict] = None,
44
+ priority: int = 0,
45
+ ) -> None:
46
+ """
47
+ Add a run to the queue.
48
+
49
+ Args:
50
+ run_id: Unique run identifier
51
+ agent_key: Agent to handle the run
52
+ input: Input data for the run
53
+ metadata: Optional metadata
54
+ priority: Priority (higher = more urgent)
55
+ """
56
+ ...
57
+
58
+ @abstractmethod
59
+ async def claim(
60
+ self,
61
+ worker_id: str,
62
+ lease_seconds: int = 60,
63
+ ) -> Optional[QueuedRun]:
64
+ """
65
+ Claim the next available run.
66
+
67
+ Args:
68
+ worker_id: ID of the claiming worker
69
+ lease_seconds: How long to hold the lease
70
+
71
+ Returns:
72
+ QueuedRun if one is available, None otherwise
73
+ """
74
+ ...
75
+
76
+ @abstractmethod
77
+ async def release(
78
+ self,
79
+ run_id: UUID,
80
+ worker_id: str,
81
+ success: bool,
82
+ output: Optional[dict] = None,
83
+ error: Optional[dict] = None,
84
+ ) -> None:
85
+ """
86
+ Release a claimed run.
87
+
88
+ Args:
89
+ run_id: Run to release
90
+ worker_id: Worker releasing the run
91
+ success: Whether the run succeeded
92
+ output: Output data (if success)
93
+ error: Error info (if failure)
94
+ """
95
+ ...
96
+
97
+ @abstractmethod
98
+ async def extend_lease(
99
+ self,
100
+ run_id: UUID,
101
+ worker_id: str,
102
+ lease_seconds: int,
103
+ ) -> bool:
104
+ """
105
+ Extend the lease on a run.
106
+
107
+ Args:
108
+ run_id: Run to extend
109
+ worker_id: Worker holding the lease
110
+ lease_seconds: New lease duration
111
+
112
+ Returns:
113
+ True if extended, False if lease was lost
114
+ """
115
+ ...
116
+
117
+ @abstractmethod
118
+ async def is_cancelled(self, run_id: UUID) -> bool:
119
+ """
120
+ Check if a run has been cancelled.
121
+
122
+ Args:
123
+ run_id: Run to check
124
+
125
+ Returns:
126
+ True if cancelled
127
+ """
128
+ ...
129
+
130
+ @abstractmethod
131
+ async def cancel(self, run_id: UUID) -> bool:
132
+ """
133
+ Cancel a run.
134
+
135
+ Args:
136
+ run_id: Run to cancel
137
+
138
+ Returns:
139
+ True if cancelled, False if not found or already complete
140
+ """
141
+ ...
142
+
143
+ @abstractmethod
144
+ async def requeue_for_retry(
145
+ self,
146
+ run_id: UUID,
147
+ worker_id: str,
148
+ error: dict,
149
+ delay_seconds: int = 0,
150
+ ) -> bool:
151
+ """
152
+ Requeue a failed run for retry.
153
+
154
+ Args:
155
+ run_id: Run to retry
156
+ worker_id: Worker releasing the run
157
+ error: Error information
158
+ delay_seconds: Delay before retry
159
+
160
+ Returns:
161
+ True if requeued, False if max retries exceeded
162
+ """
163
+ ...
164
+
165
+ async def close(self) -> None:
166
+ """Close any connections. Override if needed."""
167
+ pass
@@ -0,0 +1,184 @@
1
+ """
2
+ In-memory queue implementation.
3
+
4
+ Good for:
5
+ - Unit testing
6
+ - Local development
7
+ - Simple single-process scripts
8
+ """
9
+
10
+ import asyncio
11
+ from collections import deque
12
+ from dataclasses import dataclass, field
13
+ from datetime import datetime, timezone, timedelta
14
+ from typing import Optional
15
+ from uuid import UUID
16
+
17
+ from agent_runtime.queue.base import RunQueue, QueuedRun
18
+
19
+
20
+ @dataclass
21
+ class QueueEntry:
22
+ """Internal queue entry with lease info."""
23
+ run: QueuedRun
24
+ lease_owner: Optional[str] = None
25
+ lease_expires_at: Optional[datetime] = None
26
+ cancelled: bool = False
27
+ completed: bool = False
28
+ output: Optional[dict] = None
29
+ error: Optional[dict] = None
30
+
31
+
32
+ class InMemoryQueue(RunQueue):
33
+ """
34
+ In-memory queue implementation.
35
+
36
+ Stores runs in memory. Data is lost when the process exits.
37
+ """
38
+
39
+ def __init__(self, max_retries: int = 3):
40
+ self._entries: dict[UUID, QueueEntry] = {}
41
+ self._queue: deque[UUID] = deque()
42
+ self._lock = asyncio.Lock()
43
+ self._max_retries = max_retries
44
+
45
+ async def enqueue(
46
+ self,
47
+ run_id: UUID,
48
+ agent_key: str,
49
+ input: dict,
50
+ metadata: Optional[dict] = None,
51
+ priority: int = 0,
52
+ ) -> None:
53
+ """Add a run to the queue."""
54
+ async with self._lock:
55
+ run = QueuedRun(
56
+ run_id=run_id,
57
+ agent_key=agent_key,
58
+ input=input,
59
+ metadata=metadata or {},
60
+ priority=priority,
61
+ )
62
+ self._entries[run_id] = QueueEntry(run=run)
63
+ self._queue.append(run_id)
64
+
65
+ async def claim(
66
+ self,
67
+ worker_id: str,
68
+ lease_seconds: int = 60,
69
+ ) -> Optional[QueuedRun]:
70
+ """Claim the next available run."""
71
+ async with self._lock:
72
+ now = datetime.now(timezone.utc)
73
+
74
+ # Find an available run
75
+ for _ in range(len(self._queue)):
76
+ run_id = self._queue.popleft()
77
+ entry = self._entries.get(run_id)
78
+
79
+ if entry is None or entry.completed or entry.cancelled:
80
+ continue
81
+
82
+ # Check if lease expired
83
+ if entry.lease_owner and entry.lease_expires_at:
84
+ if entry.lease_expires_at > now:
85
+ # Still leased, put back
86
+ self._queue.append(run_id)
87
+ continue
88
+
89
+ # Claim it
90
+ entry.lease_owner = worker_id
91
+ entry.lease_expires_at = now + timedelta(seconds=lease_seconds)
92
+ return entry.run
93
+
94
+ return None
95
+
96
+ async def release(
97
+ self,
98
+ run_id: UUID,
99
+ worker_id: str,
100
+ success: bool,
101
+ output: Optional[dict] = None,
102
+ error: Optional[dict] = None,
103
+ ) -> None:
104
+ """Release a claimed run."""
105
+ async with self._lock:
106
+ entry = self._entries.get(run_id)
107
+ if entry is None:
108
+ return
109
+
110
+ if entry.lease_owner != worker_id:
111
+ return
112
+
113
+ entry.completed = True
114
+ entry.lease_owner = None
115
+ entry.lease_expires_at = None
116
+ entry.output = output
117
+ entry.error = error
118
+
119
+ async def extend_lease(
120
+ self,
121
+ run_id: UUID,
122
+ worker_id: str,
123
+ lease_seconds: int,
124
+ ) -> bool:
125
+ """Extend the lease on a run."""
126
+ async with self._lock:
127
+ entry = self._entries.get(run_id)
128
+ if entry is None:
129
+ return False
130
+
131
+ if entry.lease_owner != worker_id:
132
+ return False
133
+
134
+ entry.lease_expires_at = datetime.now(timezone.utc) + timedelta(seconds=lease_seconds)
135
+ return True
136
+
137
+ async def is_cancelled(self, run_id: UUID) -> bool:
138
+ """Check if a run has been cancelled."""
139
+ entry = self._entries.get(run_id)
140
+ return entry.cancelled if entry else False
141
+
142
+ async def cancel(self, run_id: UUID) -> bool:
143
+ """Cancel a run."""
144
+ async with self._lock:
145
+ entry = self._entries.get(run_id)
146
+ if entry is None or entry.completed:
147
+ return False
148
+
149
+ entry.cancelled = True
150
+ return True
151
+
152
+ async def requeue_for_retry(
153
+ self,
154
+ run_id: UUID,
155
+ worker_id: str,
156
+ error: dict,
157
+ delay_seconds: int = 0,
158
+ ) -> bool:
159
+ """Requeue a failed run for retry."""
160
+ async with self._lock:
161
+ entry = self._entries.get(run_id)
162
+ if entry is None:
163
+ return False
164
+
165
+ if entry.lease_owner != worker_id:
166
+ return False
167
+
168
+ if entry.run.attempt >= self._max_retries:
169
+ return False
170
+
171
+ # Increment attempt and requeue
172
+ entry.run.attempt += 1
173
+ entry.lease_owner = None
174
+ entry.lease_expires_at = None
175
+ entry.error = error
176
+
177
+ # Add back to queue
178
+ self._queue.append(run_id)
179
+ return True
180
+
181
+ def clear(self) -> None:
182
+ """Clear all entries. Useful for testing."""
183
+ self._entries.clear()
184
+ self._queue.clear()