apodex-agent-core 0.3.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.
Files changed (139) hide show
  1. agent_core/__init__.py +32 -0
  2. agent_core/components/__init__.py +1 -0
  3. agent_core/components/agent_bus/__init__.py +107 -0
  4. agent_core/components/agent_bus/agent_comm.py +278 -0
  5. agent_core/components/agent_bus/bus.py +2407 -0
  6. agent_core/components/agent_bus/fan_in.py +436 -0
  7. agent_core/components/agent_bus/models.py +246 -0
  8. agent_core/components/agent_bus/runtime.py +501 -0
  9. agent_core/components/agent_bus/shared_pool.py +43 -0
  10. agent_core/components/agent_bus/spawn_guard.py +341 -0
  11. agent_core/components/agent_bus/stop_signal.py +78 -0
  12. agent_core/components/finalization/__init__.py +53 -0
  13. agent_core/components/finalization/budget.py +267 -0
  14. agent_core/components/finalization/recovery.py +308 -0
  15. agent_core/components/middleware/__init__.py +1 -0
  16. agent_core/components/middleware/llm/__init__.py +1 -0
  17. agent_core/components/middleware/llm/base.py +233 -0
  18. agent_core/components/middleware/llm/proxy.py +276 -0
  19. agent_core/components/middleware/llm/skill_injection.py +232 -0
  20. agent_core/components/middleware/llm/stream_repetition.py +160 -0
  21. agent_core/components/middleware/llm/summarization.py +203 -0
  22. agent_core/components/observers/__init__.py +1 -0
  23. agent_core/components/observers/budget_observer.py +75 -0
  24. agent_core/components/observers/context_size_guard.py +103 -0
  25. agent_core/components/observers/finalization_reserve.py +57 -0
  26. agent_core/components/observers/last_turn_forcer.py +40 -0
  27. agent_core/components/observers/leaked_tool_call_retry.py +289 -0
  28. agent_core/components/observers/react_step_tracker.py +166 -0
  29. agent_core/components/observers/repetition_guard.py +175 -0
  30. agent_core/components/observers/sse_observer.py +170 -0
  31. agent_core/components/observers/stop_signal_observer.py +70 -0
  32. agent_core/components/observers/stuck_target_guard.py +499 -0
  33. agent_core/components/observers/task_board.py +52 -0
  34. agent_core/components/observers/text_repetition_guard.py +184 -0
  35. agent_core/components/observers/trajectory.py +650 -0
  36. agent_core/components/observers/wall_clock_guard.py +86 -0
  37. agent_core/components/observers/wall_clock_observer.py +161 -0
  38. agent_core/components/skills/__init__.py +13 -0
  39. agent_core/components/skills/allowlist_loader.py +55 -0
  40. agent_core/components/skills/config.py +49 -0
  41. agent_core/components/skills/extensions_config.py +134 -0
  42. agent_core/components/skills/file_system_loader.py +241 -0
  43. agent_core/context/__init__.py +16 -0
  44. agent_core/context/blob_store.py +63 -0
  45. agent_core/context/manager.py +81 -0
  46. agent_core/context/models.py +54 -0
  47. agent_core/context/notes.py +44 -0
  48. agent_core/context/projection.py +44 -0
  49. agent_core/context/protocols.py +40 -0
  50. agent_core/context/sqlite_store.py +209 -0
  51. agent_core/errors.py +164 -0
  52. agent_core/events.py +24 -0
  53. agent_core/execution_context.py +171 -0
  54. agent_core/llm.py +107 -0
  55. agent_core/llm_utils.py +27 -0
  56. agent_core/loop_types.py +709 -0
  57. agent_core/messages.py +267 -0
  58. agent_core/models/__init__.py +1 -0
  59. agent_core/models/agent_definition.py +51 -0
  60. agent_core/models/agent_message.py +27 -0
  61. agent_core/models/event.py +238 -0
  62. agent_core/models/node_context.py +82 -0
  63. agent_core/models/pipeline_spec.py +253 -0
  64. agent_core/models/task.py +57 -0
  65. agent_core/models/task_budget.py +133 -0
  66. agent_core/protocols.py +230 -0
  67. agent_core/providers/__init__.py +80 -0
  68. agent_core/providers/anthropic.py +724 -0
  69. agent_core/providers/aux_builder.py +202 -0
  70. agent_core/providers/fallback.py +794 -0
  71. agent_core/providers/finish_reason.py +65 -0
  72. agent_core/providers/nonblocking_stream.py +160 -0
  73. agent_core/providers/openai_chat.py +562 -0
  74. agent_core/providers/openai_responses.py +439 -0
  75. agent_core/providers/prompt_cache.py +189 -0
  76. agent_core/providers/protocol_client.py +261 -0
  77. agent_core/providers/summary.py +262 -0
  78. agent_core/py.typed +0 -0
  79. agent_core/retry_policy.py +29 -0
  80. agent_core/runtime/__init__.py +40 -0
  81. agent_core/runtime/dag/__init__.py +14 -0
  82. agent_core/runtime/dag/base_state.py +33 -0
  83. agent_core/runtime/dag/graph_builder.py +543 -0
  84. agent_core/runtime/dag/minidag.py +328 -0
  85. agent_core/runtime/env.py +35 -0
  86. agent_core/runtime/events.py +98 -0
  87. agent_core/runtime/llm_request_overrides.py +68 -0
  88. agent_core/runtime/loop/__init__.py +126 -0
  89. agent_core/runtime/loop/_bind.py +201 -0
  90. agent_core/runtime/loop/_call.py +1192 -0
  91. agent_core/runtime/loop/_response.py +468 -0
  92. agent_core/runtime/loop/_runaway.py +358 -0
  93. agent_core/runtime/loop/_streaming.py +584 -0
  94. agent_core/runtime/loop/agent_loop.py +1381 -0
  95. agent_core/runtime/loop/budget_consistency.py +128 -0
  96. agent_core/runtime/loop/compact.py +506 -0
  97. agent_core/runtime/loop/compact_llm.py +616 -0
  98. agent_core/runtime/loop/context_budget.py +113 -0
  99. agent_core/runtime/loop/guardrails.py +221 -0
  100. agent_core/runtime/loop/llm_client.py +69 -0
  101. agent_core/runtime/loop/message_trimmer.py +173 -0
  102. agent_core/runtime/loop/model_profile.py +672 -0
  103. agent_core/runtime/loop/summary_prompt.py +300 -0
  104. agent_core/runtime/loop/tiered_compact.py +626 -0
  105. agent_core/runtime/loop/tokenizer.py +81 -0
  106. agent_core/runtime/loop/tool_call_parser.py +726 -0
  107. agent_core/runtime/loop/tool_call_recovery.py +107 -0
  108. agent_core/runtime/loop/tool_call_repair.py +204 -0
  109. agent_core/runtime/loop/tool_exec.py +390 -0
  110. agent_core/runtime/pause_check.py +87 -0
  111. agent_core/runtime/registries/__init__.py +7 -0
  112. agent_core/runtime/registries/agents.py +66 -0
  113. agent_core/runtime/registries/scope.py +119 -0
  114. agent_core/runtime/registries/services.py +155 -0
  115. agent_core/runtime/registries/workflows.py +96 -0
  116. agent_core/runtime/resources/__init__.py +22 -0
  117. agent_core/runtime/resources/llm.py +54 -0
  118. agent_core/runtime/resources/manager.py +188 -0
  119. agent_core/runtime/resources/permissions.py +45 -0
  120. agent_core/runtime/retriable.py +553 -0
  121. agent_core/runtime/session_history.py +365 -0
  122. agent_core/runtime/spill.py +452 -0
  123. agent_core/runtime/tool_permission.py +151 -0
  124. agent_core/runtime/usage_meter.py +213 -0
  125. agent_core/runtime/wall_time_lease.py +243 -0
  126. agent_core/scheduling/__init__.py +27 -0
  127. agent_core/scheduling/pipeline_registry.py +32 -0
  128. agent_core/scheduling/scheduler.py +580 -0
  129. agent_core/scheduling/topology_registry.py +57 -0
  130. agent_core/scheduling/workflow_defaults.py +51 -0
  131. agent_core/tokens.py +87 -0
  132. agent_core/tool.py +297 -0
  133. agent_core/types.py +63 -0
  134. agent_core/utils/__init__.py +1 -0
  135. agent_core/utils/language.py +347 -0
  136. apodex_agent_core-0.3.0.dist-info/METADATA +267 -0
  137. apodex_agent_core-0.3.0.dist-info/RECORD +139 -0
  138. apodex_agent_core-0.3.0.dist-info/WHEEL +4 -0
  139. apodex_agent_core-0.3.0.dist-info/licenses/LICENSE +201 -0
agent_core/__init__.py ADDED
@@ -0,0 +1,32 @@
1
+ """Product-neutral building blocks for Apodex agent runtimes."""
2
+
3
+ from agent_core.errors import AgentCoreError
4
+ from agent_core.events import EventType
5
+ from agent_core.execution_context import ExecutionScope, build_execution_scope
6
+ from agent_core.llm import LLMClient, LLMResponse, StreamDelta
7
+ from agent_core.messages import (
8
+ Message,
9
+ ToolCall,
10
+ assistant_msg,
11
+ system_msg,
12
+ tool_msg,
13
+ user_msg,
14
+ )
15
+ from agent_core.types import TaskStatus
16
+
17
+ __all__ = [
18
+ "AgentCoreError",
19
+ "EventType",
20
+ "ExecutionScope",
21
+ "LLMClient",
22
+ "LLMResponse",
23
+ "Message",
24
+ "StreamDelta",
25
+ "TaskStatus",
26
+ "ToolCall",
27
+ "assistant_msg",
28
+ "build_execution_scope",
29
+ "system_msg",
30
+ "tool_msg",
31
+ "user_msg",
32
+ ]
@@ -0,0 +1 @@
1
+ """Portable agent runtime components."""
@@ -0,0 +1,107 @@
1
+ """AgentBus — multi-agent dispatch, communication, and spawn control.
2
+
3
+ Also serves as the back-compat entry point for ``agent_core.components.agent_bus``
4
+ (the former flat-file module). Re-exports the public API from submodules.
5
+ """
6
+
7
+ from agent_core.components.agent_bus.agent_comm import (
8
+ AgentComm,
9
+ DeliveryMode,
10
+ EventStoreContractError,
11
+ event_ordinal,
12
+ )
13
+ from agent_core.components.agent_bus.bus import (
14
+ AgentBus,
15
+ configure_default_event_sink_resolver,
16
+ configure_default_pause_check_factory,
17
+ configure_default_runtime_hooks,
18
+ configure_default_session_activity,
19
+ configure_default_tool_call_parser,
20
+ )
21
+ from agent_core.components.agent_bus.fan_in import (
22
+ COMPLETE_STOP_REASONS,
23
+ INCOMPLETE_STOP_REASONS,
24
+ ORCHESTRATOR_AGENT_NAME,
25
+ CompletionInfo,
26
+ CompletionPolicy,
27
+ FanInBatch,
28
+ classify_completion,
29
+ format_report_block,
30
+ format_status_line,
31
+ format_status_report_block,
32
+ process_collected,
33
+ session_state,
34
+ )
35
+ from agent_core.components.agent_bus.models import (
36
+ CollectResult,
37
+ DepthLimitExceeded,
38
+ JobEntry,
39
+ PendingSessionTask,
40
+ SessionWaitOutcome,
41
+ SubAgentResult,
42
+ SubAgentRuntimeSpec,
43
+ SubAgentSession,
44
+ SubTask,
45
+ )
46
+ from agent_core.components.agent_bus.runtime import (
47
+ adapt_default_session_result,
48
+ adapt_default_subagent_result,
49
+ build_default_subagent_loop_config,
50
+ build_default_subagent_observers,
51
+ build_session_loop_config,
52
+ close_session_boundary_aborted,
53
+ resolve_session_observers,
54
+ )
55
+ from agent_core.components.agent_bus.shared_pool import SharedArtifactPool
56
+ from agent_core.components.agent_bus.spawn_guard import (
57
+ BudgetExhausted,
58
+ SpawnDepthExceeded,
59
+ SpawnGuard,
60
+ SpawnReservation,
61
+ )
62
+
63
+ __all__ = [
64
+ "COMPLETE_STOP_REASONS",
65
+ "INCOMPLETE_STOP_REASONS",
66
+ "ORCHESTRATOR_AGENT_NAME",
67
+ "AgentBus",
68
+ "AgentComm",
69
+ "BudgetExhausted",
70
+ "CollectResult",
71
+ "CompletionInfo",
72
+ "CompletionPolicy",
73
+ "DeliveryMode",
74
+ "DepthLimitExceeded",
75
+ "EventStoreContractError",
76
+ "FanInBatch",
77
+ "JobEntry",
78
+ "PendingSessionTask",
79
+ "SessionWaitOutcome",
80
+ "SharedArtifactPool",
81
+ "SpawnDepthExceeded",
82
+ "SpawnGuard",
83
+ "SpawnReservation",
84
+ "SubAgentResult",
85
+ "SubAgentRuntimeSpec",
86
+ "SubAgentSession",
87
+ "SubTask",
88
+ "adapt_default_session_result",
89
+ "adapt_default_subagent_result",
90
+ "build_default_subagent_loop_config",
91
+ "build_default_subagent_observers",
92
+ "build_session_loop_config",
93
+ "classify_completion",
94
+ "close_session_boundary_aborted",
95
+ "configure_default_event_sink_resolver",
96
+ "configure_default_pause_check_factory",
97
+ "configure_default_runtime_hooks",
98
+ "configure_default_session_activity",
99
+ "configure_default_tool_call_parser",
100
+ "event_ordinal",
101
+ "format_report_block",
102
+ "format_status_line",
103
+ "format_status_report_block",
104
+ "process_collected",
105
+ "resolve_session_observers",
106
+ "session_state",
107
+ ]
@@ -0,0 +1,278 @@
1
+ """AgentComm — inter-agent communication service.
2
+
3
+ Sends typed AgentMessages, persists them as KernelEvents,
4
+ and publishes to EventBus for real-time streaming.
5
+
6
+ Phase D (Issue #24): DeliveryMode (TRIGGER/QUEUE) for message routing.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import itertools
13
+ import logging
14
+ from enum import StrEnum
15
+ from typing import Protocol, runtime_checkable
16
+
17
+ from agent_core.events import EventType
18
+ from agent_core.models.agent_message import AgentMessage
19
+ from agent_core.models.event import KernelEvent
20
+ from agent_core.protocols import EventReader
21
+ from agent_core.runtime.events import EventBus
22
+ from agent_core.types import TaskId
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class EventStoreContractError(RuntimeError):
28
+ """The event store violated the :class:`_AgentCommEventStore` contract.
29
+
30
+ Distinct from a transient read failure on purpose: a caller that treats
31
+ read errors as soft (``AgentBus`` result recovery does) must still
32
+ surface a misconfigured store loudly, because every future read will
33
+ fail the same way.
34
+ """
35
+
36
+
37
+ def event_ordinal(event: KernelEvent) -> int:
38
+ """Return the store-assigned total-order ordinal of ``event``.
39
+
40
+ Prefers the explicit :attr:`KernelEvent.seq`. Falls back to a decimal
41
+ ``id`` so a store that exposes its integer primary key as the event id
42
+ keeps working unchanged.
43
+
44
+ Anything else cannot carry a total order — an unset id, or the opaque
45
+ hex of :func:`agent_core.types.new_event_id`, whose values are neither
46
+ numeric nor monotonic. Those are rejected with an actionable
47
+ :class:`EventStoreContractError` rather than the bare ``ValueError``
48
+ ``int()`` would raise, because the fix is a store change and the
49
+ message has to say so.
50
+ """
51
+ if event.seq is not None:
52
+ return event.seq
53
+ raw = str(event.id)
54
+ if raw.isdecimal():
55
+ return int(raw)
56
+ raise EventStoreContractError(
57
+ "Event store must stamp every appended event with a total-order "
58
+ "ordinal: set KernelEvent.seq to a monotonically increasing integer "
59
+ "(a decimal KernelEvent.id is also accepted). Got "
60
+ f"id={raw!r}, seq=None — an opaque id such as new_event_id() cannot "
61
+ "order a durable cursor, so AgentComm.consume cannot advance safely."
62
+ )
63
+
64
+
65
+ @runtime_checkable
66
+ class _AgentCommEventStore(EventReader, Protocol):
67
+ """AgentComm needs a whole-event append plus cursored reads
68
+ (:class:`~agent_core.protocols.EventReader`) — combined here because
69
+ Python lacks an intersection type. Public Protocols stay minimal in
70
+ ``core.protocols``.
71
+
72
+ ``append`` takes a :class:`KernelEvent`, *not* the flattened
73
+ ``(task_id, event_type, payload, agent_role)`` of
74
+ :class:`~agent_core.protocols.EventSink`: an inter-agent message carries
75
+ ``from_agent`` / ``to_agent`` / ``message_type`` / ``correlation_id`` as
76
+ first-class fields that the reader queries on, and ``EventSink`` cannot
77
+ express them. A host that implements only ``EventSink`` is a valid
78
+ telemetry sink for ``AgentBus`` but is *not* a valid AgentComm store.
79
+
80
+ The combination has a stronger semantic contract than either half:
81
+ ``append`` retains the event and returns it stamped with a total-order
82
+ ordinal (see :func:`event_ordinal`), and sequential awaited appends
83
+ receive increasing ordinals. That ordinal — not ``timestamp`` — is the
84
+ total order, and ``EventReader``'s ``after_id`` cursor is expressed in
85
+ it. Concurrent appends have no defined relative order, only that every
86
+ reader observes the same resulting order.
87
+
88
+ Ordering is not guaranteed across live writer processes sharing one
89
+ JSONL path. Nor is losslessness guaranteed for a capacity-bounded
90
+ in-memory store, which evicts its oldest retained events.
91
+ """
92
+
93
+ async def append(self, event: KernelEvent) -> KernelEvent: ...
94
+
95
+
96
+ class DeliveryMode(StrEnum):
97
+ """Message delivery mode.
98
+
99
+ TRIGGER: persist + hot queue + EventBus broadcast.
100
+ Use when receiver must act immediately (e.g., assertion → critic).
101
+ QUEUE: persist + hot queue only (no broadcast).
102
+ Use for status updates the receiver pulls when ready.
103
+ """
104
+
105
+ TRIGGER = "trigger"
106
+ QUEUE = "queue"
107
+
108
+
109
+ class AgentComm:
110
+ """Sends inter-agent messages and logs them as events.
111
+
112
+ All messages are persisted to EventStore (truth source).
113
+ Hot queues are optional in-memory acceleration.
114
+ DeliveryMode controls whether EventBus broadcast fires.
115
+ """
116
+
117
+ def __init__(
118
+ self,
119
+ event_store: _AgentCommEventStore,
120
+ event_bus: EventBus,
121
+ ) -> None:
122
+ self._event_store = event_store
123
+ self._event_bus = event_bus
124
+ # Each message type gets an independent cursor. A specialized
125
+ # consumer (for example AgentBus result recovery) must not advance
126
+ # the generic inbox cursor or consume status/control messages that
127
+ # belong to another component.
128
+ self._cursors: dict[tuple[str, str | None, str | None], int] = {}
129
+ self._hot_queues: dict[str, asyncio.Queue[KernelEvent]] = {}
130
+
131
+ async def send(
132
+ self,
133
+ msg: AgentMessage,
134
+ mode: DeliveryMode = DeliveryMode.QUEUE,
135
+ ) -> KernelEvent:
136
+ """Send an agent message.
137
+
138
+ Always persists to EventStore (truth source).
139
+ TRIGGER mode additionally broadcasts via EventBus for immediate wakeup.
140
+ """
141
+ event = KernelEvent(
142
+ task_id=TaskId(msg.task_id),
143
+ event_type=EventType.AGENT_MESSAGE,
144
+ from_agent=msg.from_agent,
145
+ to_agent=msg.to_agent,
146
+ message_type=msg.message_type,
147
+ correlation_id=msg.content.get("correlation_id"),
148
+ payload={
149
+ "message_id": msg.id,
150
+ "from_agent": msg.from_agent,
151
+ "to_agent": msg.to_agent,
152
+ "message_type": msg.message_type,
153
+ "content": msg.content,
154
+ "parent_id": msg.parent_id,
155
+ "correlation_id": msg.content.get("correlation_id"),
156
+ "delivery_mode": mode.value,
157
+ },
158
+ )
159
+
160
+ # 1. Persist (always — EventStore is truth source)
161
+ persisted = await self._event_store.append(event)
162
+
163
+ # 2. Hot queue (always — non-durable acceleration)
164
+ queue = self._hot_queues.get(msg.to_agent)
165
+ if queue is not None:
166
+ try:
167
+ queue.put_nowait(persisted)
168
+ except asyncio.QueueFull:
169
+ logger.debug(
170
+ "Hot queue for %s is full; consumer uses EventStore",
171
+ msg.to_agent,
172
+ )
173
+
174
+ # 3. Broadcast (TRIGGER only — immediate wakeup signal)
175
+ if mode == DeliveryMode.TRIGGER:
176
+ await self._event_bus.publish(
177
+ event.event_type, event.payload,
178
+ )
179
+
180
+ logger.debug(
181
+ "Agent message %s: %s → %s [%s] mode=%s",
182
+ msg.id, msg.from_agent, msg.to_agent,
183
+ msg.message_type, mode.value,
184
+ )
185
+ return persisted
186
+
187
+ async def consume(
188
+ self,
189
+ agent_id: str,
190
+ *,
191
+ task_id: str | None = None,
192
+ limit: int = 50,
193
+ message_type: str | None = None,
194
+ ) -> list[KernelEvent]:
195
+ """Consume undelivered messages for an agent using an idempotent cursor.
196
+
197
+ Cursor is per ``(agent_id, task_id, message_type)``. Typed consumers
198
+ therefore cannot skip or discard unrelated inbox traffic. Restarting
199
+ from 0 replays all messages in the selected stream.
200
+
201
+ Ordering (L2-2): messages come back in send order within a task.
202
+ That rests entirely on the stronger ``_AgentCommEventStore``
203
+ contract above and the "Ordering" section of
204
+ ``core.protocols.EventReader``. Three consequences callers rely on:
205
+
206
+ * a sender that awaits each ``send`` sees its own messages here
207
+ in exactly that order — never reordered, never with a later
208
+ message ahead of an earlier one;
209
+ * the cursor only moves forward, so each message reaches this
210
+ ``(agent, task, message_type)`` reader exactly once even when
211
+ ``limit`` truncates the batch — the remainder arrives on the
212
+ next call, still in order;
213
+ * ``message_type`` is part of the cursor key on purpose: a
214
+ shared cursor would let one consumer's read carry another
215
+ consumer's mail past unread.
216
+
217
+ ``tests/test_agent_comm_store_contract.py`` pins all three, plus
218
+ the ordinal requirement itself, against a reference store.
219
+ """
220
+ cursor_key = (agent_id, task_id, message_type)
221
+ after_id = self._cursors.get(cursor_key, 0)
222
+ # Preserve compatibility with third-party EventReader implementations
223
+ # written before typed inbox queries existed.
224
+ if message_type is None:
225
+ events = await self._event_store.get_events_for_agent(
226
+ agent_id,
227
+ after_id=after_id,
228
+ limit=limit,
229
+ task_id=task_id,
230
+ )
231
+ else:
232
+ events = await self._event_store.get_events_for_agent(
233
+ agent_id,
234
+ after_id=after_id,
235
+ limit=limit,
236
+ task_id=task_id,
237
+ message_type=message_type,
238
+ )
239
+ if events:
240
+ ids = [event_ordinal(event) for event in events]
241
+ if any(current <= previous for previous, current in itertools.pairwise(ids)):
242
+ # Advancing to max(ids) can permanently skip lower ids that
243
+ # a misordered, limited query left outside this batch. Keep
244
+ # the cursor untouched and fail loudly instead of returning
245
+ # a partial stream with silent data loss.
246
+ logger.error(
247
+ "Event store returned %d events for %s outside strict "
248
+ "ascending id order (%s); cursor remains at %d",
249
+ len(ids), agent_id, ids, after_id,
250
+ )
251
+ raise RuntimeError(
252
+ "EventReader must return events in strictly ascending id order"
253
+ )
254
+ self._cursors[cursor_key] = ids[-1]
255
+ return events
256
+
257
+ def reset_cursor(
258
+ self,
259
+ agent_id: str,
260
+ task_id: str | None = None,
261
+ *,
262
+ message_type: str | None = None,
263
+ ) -> None:
264
+ """Reset one typed cursor, or every cursor for an agent/task pair."""
265
+ if message_type is not None:
266
+ self._cursors.pop((agent_id, task_id, message_type), None)
267
+ return
268
+ for key in list(self._cursors):
269
+ if key[:2] == (agent_id, task_id):
270
+ self._cursors.pop(key, None)
271
+
272
+ def hot_queue_for(
273
+ self, agent_id: str,
274
+ ) -> asyncio.Queue[KernelEvent]:
275
+ """Return the non-durable hot queue for an agent."""
276
+ return self._hot_queues.setdefault(
277
+ agent_id, asyncio.Queue(maxsize=256),
278
+ )