monoid-agent-kernel 0.16.1__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 (191) hide show
  1. monoid_agent_kernel/__init__.py +11 -0
  2. monoid_agent_kernel/_policy_util.py +40 -0
  3. monoid_agent_kernel/_proc.py +86 -0
  4. monoid_agent_kernel/builder.py +351 -0
  5. monoid_agent_kernel/cli.py +1244 -0
  6. monoid_agent_kernel/conformance/__init__.py +32 -0
  7. monoid_agent_kernel/conformance/harness.py +209 -0
  8. monoid_agent_kernel/conformance/profiles/__init__.py +55 -0
  9. monoid_agent_kernel/conformance/profiles/_metadata.py +16 -0
  10. monoid_agent_kernel/conformance/profiles/capability_security.py +216 -0
  11. monoid_agent_kernel/conformance/profiles/control_plane.py +41 -0
  12. monoid_agent_kernel/conformance/profiles/durable_runner.py +42 -0
  13. monoid_agent_kernel/conformance/profiles/message_fabric.py +42 -0
  14. monoid_agent_kernel/conformance/profiles/minimal_agent.py +13 -0
  15. monoid_agent_kernel/conformance/profiles/multi_agent.py +112 -0
  16. monoid_agent_kernel/conformance/profiles/provider_gateway.py +107 -0
  17. monoid_agent_kernel/conformance/profiles/reference_full.py +135 -0
  18. monoid_agent_kernel/conformance/profiles/side_effect_tool_agent.py +36 -0
  19. monoid_agent_kernel/conformance/profiles/tool_agent.py +39 -0
  20. monoid_agent_kernel/contracts.py +329 -0
  21. monoid_agent_kernel/core/__init__.py +1 -0
  22. monoid_agent_kernel/core/_util.py +145 -0
  23. monoid_agent_kernel/core/agents.py +883 -0
  24. monoid_agent_kernel/core/cancellation.py +16 -0
  25. monoid_agent_kernel/core/capability.py +376 -0
  26. monoid_agent_kernel/core/capability_revocation.py +92 -0
  27. monoid_agent_kernel/core/checkpoint.py +350 -0
  28. monoid_agent_kernel/core/content.py +101 -0
  29. monoid_agent_kernel/core/context.py +67 -0
  30. monoid_agent_kernel/core/control.py +124 -0
  31. monoid_agent_kernel/core/control_audit.py +99 -0
  32. monoid_agent_kernel/core/durable_metadata.py +116 -0
  33. monoid_agent_kernel/core/event_sequencing.py +164 -0
  34. monoid_agent_kernel/core/events.py +204 -0
  35. monoid_agent_kernel/core/external_agent_envelope.py +338 -0
  36. monoid_agent_kernel/core/frontmatter.py +140 -0
  37. monoid_agent_kernel/core/inbox.py +110 -0
  38. monoid_agent_kernel/core/lease_admission.py +57 -0
  39. monoid_agent_kernel/core/lifecycle.py +440 -0
  40. monoid_agent_kernel/core/manifest.py +88 -0
  41. monoid_agent_kernel/core/media.py +393 -0
  42. monoid_agent_kernel/core/outbox.py +212 -0
  43. monoid_agent_kernel/core/output_validator.py +103 -0
  44. monoid_agent_kernel/core/packages.py +742 -0
  45. monoid_agent_kernel/core/projections.py +213 -0
  46. monoid_agent_kernel/core/prompt.py +37 -0
  47. monoid_agent_kernel/core/proposal_file.py +84 -0
  48. monoid_agent_kernel/core/result.py +131 -0
  49. monoid_agent_kernel/core/schemas.py +1146 -0
  50. monoid_agent_kernel/core/scope.py +185 -0
  51. monoid_agent_kernel/core/side_effect_policy.py +153 -0
  52. monoid_agent_kernel/core/spec.py +325 -0
  53. monoid_agent_kernel/core/streaming.py +208 -0
  54. monoid_agent_kernel/core/subagent_runtime.py +187 -0
  55. monoid_agent_kernel/core/tool_approval.py +175 -0
  56. monoid_agent_kernel/core/tool_surface.py +505 -0
  57. monoid_agent_kernel/core/trace_context.py +76 -0
  58. monoid_agent_kernel/core/wire_validation.py +156 -0
  59. monoid_agent_kernel/core/workspace.py +161 -0
  60. monoid_agent_kernel/core/workspace_index.py +127 -0
  61. monoid_agent_kernel/env.py +32 -0
  62. monoid_agent_kernel/errors.py +105 -0
  63. monoid_agent_kernel/event_loader.py +47 -0
  64. monoid_agent_kernel/identifiers.py +50 -0
  65. monoid_agent_kernel/loop.py +4140 -0
  66. monoid_agent_kernel/loop_phases.py +561 -0
  67. monoid_agent_kernel/mcp/__init__.py +10 -0
  68. monoid_agent_kernel/mcp/client.py +227 -0
  69. monoid_agent_kernel/mcp/provider.py +407 -0
  70. monoid_agent_kernel/narration.py +92 -0
  71. monoid_agent_kernel/observability/__init__.py +9 -0
  72. monoid_agent_kernel/observability/otel.py +264 -0
  73. monoid_agent_kernel/permissions.py +74 -0
  74. monoid_agent_kernel/providers/__init__.py +7 -0
  75. monoid_agent_kernel/providers/_common.py +122 -0
  76. monoid_agent_kernel/providers/base.py +312 -0
  77. monoid_agent_kernel/providers/fake.py +76 -0
  78. monoid_agent_kernel/providers/gateway.py +474 -0
  79. monoid_agent_kernel/providers/openai.py +487 -0
  80. monoid_agent_kernel/public_view.py +173 -0
  81. monoid_agent_kernel/py.typed +0 -0
  82. monoid_agent_kernel/recorder.py +421 -0
  83. monoid_agent_kernel/reference/__init__.py +15 -0
  84. monoid_agent_kernel/reference/_shared/__init__.py +4 -0
  85. monoid_agent_kernel/reference/_shared/http_util.py +111 -0
  86. monoid_agent_kernel/reference/_shared/tokens.py +314 -0
  87. monoid_agent_kernel/reference/backend/__init__.py +21 -0
  88. monoid_agent_kernel/reference/backend/commands.py +375 -0
  89. monoid_agent_kernel/reference/backend/http.py +439 -0
  90. monoid_agent_kernel/reference/backend/jobs.py +68 -0
  91. monoid_agent_kernel/reference/backend/loop_factory.py +253 -0
  92. monoid_agent_kernel/reference/backend/outbox_dispatch.py +130 -0
  93. monoid_agent_kernel/reference/backend/ports.py +188 -0
  94. monoid_agent_kernel/reference/backend/projection.py +286 -0
  95. monoid_agent_kernel/reference/backend/proposal.py +220 -0
  96. monoid_agent_kernel/reference/backend/proposal_reader.py +16 -0
  97. monoid_agent_kernel/reference/backend/recovery.py +253 -0
  98. monoid_agent_kernel/reference/backend/run_execution.py +152 -0
  99. monoid_agent_kernel/reference/backend/run_preparation.py +197 -0
  100. monoid_agent_kernel/reference/backend/run_state.py +243 -0
  101. monoid_agent_kernel/reference/backend/run_types.py +128 -0
  102. monoid_agent_kernel/reference/backend/runtime_config.py +147 -0
  103. monoid_agent_kernel/reference/backend/service.py +1664 -0
  104. monoid_agent_kernel/reference/backend/session.py +280 -0
  105. monoid_agent_kernel/reference/backend/session_drive.py +231 -0
  106. monoid_agent_kernel/reference/capability.py +101 -0
  107. monoid_agent_kernel/reference/conformance.py +1777 -0
  108. monoid_agent_kernel/reference/llm_gateway/__init__.py +14 -0
  109. monoid_agent_kernel/reference/llm_gateway/http.py +225 -0
  110. monoid_agent_kernel/reference/llm_gateway/providers.py +96 -0
  111. monoid_agent_kernel/reference/llm_gateway/service.py +404 -0
  112. monoid_agent_kernel/reference/mcp_gateway/__init__.py +26 -0
  113. monoid_agent_kernel/reference/mcp_gateway/http.py +187 -0
  114. monoid_agent_kernel/reference/mcp_gateway/service.py +206 -0
  115. monoid_agent_kernel/reference/outbox.py +135 -0
  116. monoid_agent_kernel/reference/stores/__init__.py +20 -0
  117. monoid_agent_kernel/reference/stores/lease.py +116 -0
  118. monoid_agent_kernel/reference/stores/sqlite.py +295 -0
  119. monoid_agent_kernel/reference/studio/README.md +97 -0
  120. monoid_agent_kernel/reference/studio/__init__.py +21 -0
  121. monoid_agent_kernel/reference/studio/activity.py +53 -0
  122. monoid_agent_kernel/reference/studio/cli.py +481 -0
  123. monoid_agent_kernel/reference/studio/sample-skills/code-review-checklist/SKILL.md +25 -0
  124. monoid_agent_kernel/reference/studio/sample-skills/code-review-checklist/references/review-checklist.md +8 -0
  125. monoid_agent_kernel/reference/studio/sample-skills/commit-message/SKILL.md +32 -0
  126. monoid_agent_kernel/reference/studio/sample-skills/incident-summary/SKILL.md +24 -0
  127. monoid_agent_kernel/reference/studio/sample-skills/incident-summary/references/incident-template.md +32 -0
  128. monoid_agent_kernel/reference/studio/sample-skills/release-notes/SKILL.md +24 -0
  129. monoid_agent_kernel/reference/studio/sample-skills/release-notes/references/release-note-template.md +33 -0
  130. monoid_agent_kernel/reference/studio/sample-skills/release-notes/scripts/collect_changes.py +51 -0
  131. monoid_agent_kernel/reference/studio/server.py +1922 -0
  132. monoid_agent_kernel/reference/studio/web/index.html +1877 -0
  133. monoid_agent_kernel/reference/studio/web/settings.html +108 -0
  134. monoid_agent_kernel/reference/studio/web/vendor/katex/auto-render.min.js +1 -0
  135. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  136. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  137. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  138. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  139. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  140. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  141. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  142. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  143. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  144. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  145. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  146. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  147. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  148. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  149. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  150. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  151. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  152. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  153. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  154. monoid_agent_kernel/reference/studio/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  155. monoid_agent_kernel/reference/studio/web/vendor/katex/katex.min.css +1 -0
  156. monoid_agent_kernel/reference/studio/web/vendor/katex/katex.min.js +1 -0
  157. monoid_agent_kernel/reference/studio/window.py +72 -0
  158. monoid_agent_kernel/reference/web_gateway/__init__.py +6 -0
  159. monoid_agent_kernel/reference/web_gateway/http.py +155 -0
  160. monoid_agent_kernel/reference/web_gateway/providers.py +807 -0
  161. monoid_agent_kernel/reference/web_gateway/service.py +559 -0
  162. monoid_agent_kernel/shell.py +722 -0
  163. monoid_agent_kernel/skills/__init__.py +20 -0
  164. monoid_agent_kernel/skills/definition.py +82 -0
  165. monoid_agent_kernel/skills/loader.py +55 -0
  166. monoid_agent_kernel/skills/provider.py +378 -0
  167. monoid_agent_kernel/subagent_loader.py +56 -0
  168. monoid_agent_kernel/tasks.py +1326 -0
  169. monoid_agent_kernel/tool_loader.py +51 -0
  170. monoid_agent_kernel/tool_services/__init__.py +8 -0
  171. monoid_agent_kernel/tool_services/base.py +25 -0
  172. monoid_agent_kernel/tool_services/jobs.py +47 -0
  173. monoid_agent_kernel/tool_services/shell.py +255 -0
  174. monoid_agent_kernel/tool_services/web.py +356 -0
  175. monoid_agent_kernel/tools/__init__.py +2 -0
  176. monoid_agent_kernel/tools/base.py +205 -0
  177. monoid_agent_kernel/tools/builtin.py +958 -0
  178. monoid_agent_kernel/tools/decorator.py +132 -0
  179. monoid_agent_kernel/tools/tool_ids.py +62 -0
  180. monoid_agent_kernel/web.py +171 -0
  181. monoid_agent_kernel/workspace/__init__.py +2 -0
  182. monoid_agent_kernel/workspace/local.py +1004 -0
  183. monoid_agent_kernel/workspace/paths.py +30 -0
  184. monoid_agent_kernel-0.16.1.dist-info/METADATA +709 -0
  185. monoid_agent_kernel-0.16.1.dist-info/RECORD +191 -0
  186. monoid_agent_kernel-0.16.1.dist-info/WHEEL +4 -0
  187. monoid_agent_kernel-0.16.1.dist-info/entry_points.txt +3 -0
  188. monoid_agent_kernel-0.16.1.dist-info/licenses/LICENSE +202 -0
  189. monoid_agent_kernel-0.16.1.dist-info/licenses/NOTICE +8 -0
  190. native_agent_runner/__init__.py +103 -0
  191. native_agent_runner/py.typed +0 -0
@@ -0,0 +1,253 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from collections.abc import Callable, Mapping
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from monoid_agent_kernel.core.agents import AgentRuntimeConfig, RuntimeConfigProvider
10
+ from monoid_agent_kernel.core.capability import CapabilityBroker
11
+ from monoid_agent_kernel.core.checkpoint import CheckpointStore, RunCheckpoint
12
+ from monoid_agent_kernel.core.context import ContextProvider
13
+ from monoid_agent_kernel.core.events import AgentEvent, EventSink
14
+ from monoid_agent_kernel.core.outbox import OutboxSender
15
+ from monoid_agent_kernel.core.output_validator import OutputValidator
16
+ from monoid_agent_kernel.core.spec import AgentRunSpec, ModelConfig, RunLimits
17
+ from monoid_agent_kernel.loop import AgentLoop
18
+ from monoid_agent_kernel.providers.base import ModelAdapter
19
+ from monoid_agent_kernel.providers.gateway import GatewayModelAdapter
20
+ from monoid_agent_kernel.reference._shared.tokens import TokenKind, TokenManager
21
+ from monoid_agent_kernel.reference.backend.ports import MutableRunRecordPort, RunRequestPort
22
+ from monoid_agent_kernel.reference.backend.run_state import BackendRunStateSink
23
+ from monoid_agent_kernel.tools.base import ToolProvider
24
+ from monoid_agent_kernel.web import WebGatewayClient
25
+
26
+ ModelAdapterFactory = Callable[[AgentRunSpec, str], ModelAdapter]
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class BackendLoopBuild:
31
+ spec: AgentRunSpec
32
+ model_adapter: ModelAdapter
33
+ web_gateway_client: WebGatewayClient | None
34
+ runtime_config_provider: RuntimeConfigProvider
35
+ capability_broker: CapabilityBroker | None
36
+ outbox_sender: OutboxSender | None
37
+ loop: AgentLoop
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class BackendLoopFactoryContext:
42
+ run_root_provider: Callable[[], Path]
43
+ llm_gateway_url_provider: Callable[[], str]
44
+ web_gateway_url_provider: Callable[[], str | None]
45
+ model_adapter_factory_provider: Callable[[], ModelAdapterFactory | None]
46
+ token_manager_provider: Callable[[], TokenManager]
47
+ llm_gateway_token_ttl_s_provider: Callable[[], int]
48
+ checkpoint_store_provider: Callable[[], CheckpointStore | None]
49
+ emit_output_deltas_provider: Callable[[], bool]
50
+ extra_event_sink_factories_provider: Callable[[], tuple[Callable[[], EventSink], ...]]
51
+ subagent_definitions_provider: Callable[[], Mapping[str, Any] | None]
52
+ tool_providers_provider: Callable[[], tuple[ToolProvider, ...]]
53
+ context_providers_provider: Callable[[], tuple[ContextProvider, ...]]
54
+ output_validators_provider: Callable[[], tuple[OutputValidator, ...]]
55
+ capability_broker_factory_provider: Callable[[], Callable[[RunRequestPort], CapabilityBroker | None] | None]
56
+ outbox_sender_factory_provider: Callable[[], Callable[[RunRequestPort], OutboxSender | None] | None]
57
+ current_runtime_config: Callable[[str], AgentRuntimeConfig | None]
58
+ record: Callable[[str], MutableRunRecordPort]
59
+ record_event: Callable[[str, AgentEvent], None]
60
+ persist_checkpoint_payload: Callable[[MutableRunRecordPort, RunCheckpoint, Mapping[str, bytes]], None]
61
+
62
+
63
+ class BackendRuntimeConfigProvider(RuntimeConfigProvider):
64
+ def __init__(
65
+ self,
66
+ current_runtime_config: Callable[[str], AgentRuntimeConfig | None],
67
+ run_id: str,
68
+ ) -> None:
69
+ self._current_runtime_config = current_runtime_config
70
+ self._run_id = run_id
71
+
72
+ def current_config(self, run_id: str) -> AgentRuntimeConfig | None:
73
+ del run_id
74
+ return self._current_runtime_config(self._run_id)
75
+
76
+
77
+ @dataclass
78
+ class _GatewayTokenSource:
79
+ """Callable gateway token source that re-mints shortly before expiry for long runs."""
80
+
81
+ token_manager: TokenManager
82
+ kind: TokenKind
83
+ audience: str
84
+ run_id: str
85
+ tenant_id: str
86
+ user_id: str
87
+ ttl_s: int
88
+ metadata: dict[str, Any] = field(default_factory=dict)
89
+ refresh_skew_s: int = 300
90
+ _token: str = ""
91
+ _expires_at: float = 0.0
92
+
93
+ def __call__(self) -> str:
94
+ now = time.time()
95
+ skew = min(self.refresh_skew_s, self.ttl_s // 2)
96
+ if not self._token or now >= self._expires_at - skew:
97
+ self._token = self.token_manager.issue(
98
+ kind=self.kind,
99
+ audience=self.audience,
100
+ run_id=self.run_id,
101
+ tenant_id=self.tenant_id,
102
+ user_id=self.user_id,
103
+ ttl_s=self.ttl_s,
104
+ metadata=dict(self.metadata),
105
+ )
106
+ self._expires_at = now + self.ttl_s
107
+ return self._token
108
+
109
+
110
+ class BackendLoopFactory:
111
+ """Builds AgentLoop instances for the Reference backend facade."""
112
+
113
+ def __init__(self, context: BackendLoopFactoryContext) -> None:
114
+ self._context = context
115
+
116
+ def build(
117
+ self,
118
+ run_id: str,
119
+ request: RunRequestPort,
120
+ workspace_root: Path,
121
+ llm_gateway_token: str,
122
+ web_gateway_token: str,
123
+ *,
124
+ include_outbox_sender: bool = True,
125
+ ) -> BackendLoopBuild:
126
+ spec = self.run_spec_for_request(run_id, request, workspace_root)
127
+ runtime_config = self._context.current_runtime_config(run_id)
128
+ model_adapter = self.build_model_adapter(
129
+ spec,
130
+ llm_gateway_token,
131
+ runtime_config.model if runtime_config is not None else None,
132
+ token_provider=self.llm_token_source(run_id, request, runtime_config),
133
+ )
134
+ web_gateway_client = self.web_gateway_client(web_gateway_token)
135
+ runtime_config_provider = BackendRuntimeConfigProvider(
136
+ self._context.current_runtime_config,
137
+ run_id,
138
+ )
139
+ capability_broker = self.capability_broker_for(request)
140
+ outbox_sender = self.outbox_sender_for(request) if include_outbox_sender else None
141
+ record = self._context.record(run_id)
142
+ loop = AgentLoop(
143
+ spec=spec,
144
+ model_adapter=model_adapter,
145
+ event_sinks=(
146
+ BackendRunStateSink(self._context.record_event, run_id),
147
+ *(make() for make in self._context.extra_event_sink_factories_provider()),
148
+ ),
149
+ permission_policy=request.permission_policy,
150
+ cancellation_token=record.cancellation_token,
151
+ shell_approval_provider=None,
152
+ web_gateway_client=web_gateway_client,
153
+ runtime_config_provider=runtime_config_provider,
154
+ checkpoint_store=self._context.checkpoint_store_provider(),
155
+ emit_output_deltas=self._context.emit_output_deltas_provider(),
156
+ subagent_definitions=self._context.subagent_definitions_provider(),
157
+ tool_providers=self._context.tool_providers_provider(),
158
+ context_providers=self._context.context_providers_provider(),
159
+ output_validators=self._context.output_validators_provider(),
160
+ capability_broker=capability_broker,
161
+ checkpoint_persist_callback=lambda checkpoint, blobs: self._context.persist_checkpoint_payload(
162
+ self._context.record(run_id),
163
+ checkpoint,
164
+ blobs,
165
+ ),
166
+ )
167
+ return BackendLoopBuild(
168
+ spec=spec,
169
+ model_adapter=model_adapter,
170
+ web_gateway_client=web_gateway_client,
171
+ runtime_config_provider=runtime_config_provider,
172
+ capability_broker=capability_broker,
173
+ outbox_sender=outbox_sender,
174
+ loop=loop,
175
+ )
176
+
177
+ def run_spec_for_request(
178
+ self,
179
+ run_id: str,
180
+ request: RunRequestPort,
181
+ workspace_root: Path,
182
+ ) -> AgentRunSpec:
183
+ return AgentRunSpec(
184
+ workspace_root=workspace_root,
185
+ run_root=self._context.run_root_provider(),
186
+ run_id=run_id,
187
+ mode=request.mode,
188
+ workspace_backend=request.workspace_backend,
189
+ limits=RunLimits(
190
+ max_steps=request.max_steps,
191
+ max_tool_calls=request.max_tool_calls,
192
+ max_bytes_read=request.max_bytes_read,
193
+ max_duration_s=request.max_duration_s,
194
+ ),
195
+ permission_policy=request.permission_policy,
196
+ metadata={
197
+ **request.metadata,
198
+ "tenant_id": request.tenant_id,
199
+ "user_id": request.user_id,
200
+ },
201
+ )
202
+
203
+ def capability_broker_for(self, request: RunRequestPort) -> CapabilityBroker | None:
204
+ factory = self._context.capability_broker_factory_provider()
205
+ if factory is None:
206
+ return None
207
+ return factory(request)
208
+
209
+ def outbox_sender_for(self, request: RunRequestPort) -> OutboxSender | None:
210
+ factory = self._context.outbox_sender_factory_provider()
211
+ if factory is None:
212
+ return None
213
+ return factory(request)
214
+
215
+ def llm_token_source(
216
+ self,
217
+ run_id: str,
218
+ request: RunRequestPort,
219
+ runtime_config: AgentRuntimeConfig | None,
220
+ ) -> _GatewayTokenSource:
221
+ return _GatewayTokenSource(
222
+ token_manager=self._context.token_manager_provider(),
223
+ kind="llm_gateway",
224
+ audience="csp.llm-gateway",
225
+ run_id=run_id,
226
+ tenant_id=request.tenant_id,
227
+ user_id=request.user_id,
228
+ ttl_s=self._context.llm_gateway_token_ttl_s_provider(),
229
+ metadata={"agent_config_hash": runtime_config.config_hash} if runtime_config is not None else {},
230
+ )
231
+
232
+ def build_model_adapter(
233
+ self,
234
+ spec: AgentRunSpec,
235
+ llm_gateway_token: str,
236
+ model_config: ModelConfig | None,
237
+ token_provider: Callable[[], str | None] | None = None,
238
+ ) -> ModelAdapter:
239
+ factory = self._context.model_adapter_factory_provider()
240
+ if factory is not None:
241
+ return factory(spec, llm_gateway_token)
242
+ return GatewayModelAdapter(
243
+ model_config or ModelConfig(),
244
+ gateway_url=self._context.llm_gateway_url_provider(),
245
+ token=llm_gateway_token,
246
+ token_provider=token_provider,
247
+ )
248
+
249
+ def web_gateway_client(self, token: str) -> WebGatewayClient | None:
250
+ gateway_url = self._context.web_gateway_url_provider()
251
+ if not token or not gateway_url:
252
+ return None
253
+ return WebGatewayClient(gateway_url, token=token)
@@ -0,0 +1,130 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ import time
5
+ from collections.abc import Callable, Mapping
6
+ from dataclasses import dataclass
7
+ from typing import Any, Protocol
8
+
9
+ from monoid_agent_kernel.core.checkpoint import CheckpointStore, RunCheckpoint
10
+ from monoid_agent_kernel.core.inbox import InboxMessage
11
+ from monoid_agent_kernel.core.outbox import OutboxReceipt, OutboxRequest
12
+ from monoid_agent_kernel.core.trace_context import new_traceparent
13
+ from monoid_agent_kernel.reference.backend.ports import MutableRunRecordPort, queued_message_snapshot
14
+
15
+
16
+ class OutboxLoopPort(Protocol):
17
+ def due_outbox(self, now: float) -> list[OutboxRequest]: ...
18
+
19
+ def record_outbox_result(
20
+ self,
21
+ request_id: str,
22
+ receipt: OutboxReceipt,
23
+ *,
24
+ max_attempts: int = 5,
25
+ next_attempt_at: float = 0.0,
26
+ ) -> str: ...
27
+
28
+ def snapshot(self) -> RunCheckpoint | None: ...
29
+
30
+ def collect_checkpoint_blobs(self) -> Mapping[str, bytes]: ...
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class OutboxRetryPolicy:
35
+ max_attempts: int
36
+ base_s: float
37
+ factor: float
38
+ cap_s: float
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class OutboxDispatchContext:
43
+ retry_policy_provider: Callable[[], OutboxRetryPolicy]
44
+ max_message_queue_depth_provider: Callable[[], int]
45
+ checkpoint_store_provider: Callable[[], CheckpointStore]
46
+ rng_provider: Callable[[], random.Random]
47
+ live_outbox_runs: Callable[[], list[tuple[MutableRunRecordPort, OutboxLoopPort]]]
48
+ call_soon: Callable[..., None]
49
+ record_terminal: Callable[[MutableRunRecordPort], bool]
50
+
51
+
52
+ class OutboxDispatchService:
53
+ """Reference backend edge dispatcher for staged outbox side effects."""
54
+
55
+ def __init__(self, context: OutboxDispatchContext) -> None:
56
+ self._context = context
57
+
58
+ def backoff_delay(self, attempts: int) -> float:
59
+ """Capped exponential backoff with full jitter."""
60
+ policy = self._context.retry_policy_provider()
61
+ ceiling = min(policy.cap_s, policy.base_s * (policy.factor**attempts))
62
+ return self._context.rng_provider().uniform(0.0, max(0.0, ceiling))
63
+
64
+ def drain_outbox(self, record: MutableRunRecordPort, loop: OutboxLoopPort) -> None:
65
+ """Dispatch due staged outbox requests and persist the resulting checkpoint state."""
66
+ sender = record.outbox_sender
67
+ now = time.time()
68
+ due = loop.due_outbox(now)
69
+ if sender is None or not due:
70
+ return
71
+ changed = False
72
+ policy = self._context.retry_policy_provider()
73
+ for request in due:
74
+ if not request.traceparent:
75
+ request.traceparent = new_traceparent()
76
+ try:
77
+ receipt = sender.send(request)
78
+ except Exception as exc: # a sender raising is a retryable transport failure
79
+ receipt = OutboxReceipt(ok=False, error=str(exc), retryable=True)
80
+ next_attempt_at = now + self.backoff_delay(request.attempts + 1)
81
+ status = loop.record_outbox_result(
82
+ request.id,
83
+ receipt,
84
+ max_attempts=policy.max_attempts,
85
+ next_attempt_at=next_attempt_at,
86
+ )
87
+ changed = True
88
+ if request.expect_ack and status in {"dispatched", "failed"}:
89
+ self.stage_outbox_ack(record, request, status, receipt)
90
+ if changed:
91
+ checkpoint = loop.snapshot()
92
+ if checkpoint is not None:
93
+ checkpoint.queued_messages = queued_message_snapshot(record.message_queue)
94
+ checkpoint.inbox_seen_ids = sorted(record.seen_inbox_ids)
95
+ self._context.checkpoint_store_provider().put(checkpoint, loop.collect_checkpoint_blobs())
96
+
97
+ def stage_outbox_ack(
98
+ self, record: MutableRunRecordPort, request: Any, status: str, receipt: OutboxReceipt
99
+ ) -> None:
100
+ """Deliver an outbox send receipt back into the run inbox as a correlated message."""
101
+ ack_id = f"ack_{request.id}"
102
+ if self._context.record_terminal(record):
103
+ return
104
+ if (
105
+ ack_id in record.seen_inbox_ids
106
+ or record.message_queue.qsize() >= self._context.max_message_queue_depth_provider()
107
+ ):
108
+ return
109
+ summary = f"[outbox-ack] request {request.id} to {request.destination!r}: {status}"
110
+ if receipt.reference:
111
+ summary += f" (ref={receipt.reference})"
112
+ if receipt.error:
113
+ summary += f" (error={receipt.error})"
114
+ envelope = InboxMessage(
115
+ content=summary,
116
+ id=ack_id,
117
+ source="outbox",
118
+ type="outbox_ack",
119
+ run_id=record.run_id,
120
+ correlation_id=request.correlation_id or request.id,
121
+ causation_id=request.id,
122
+ traceparent=request.traceparent,
123
+ tracestate=request.tracestate,
124
+ )
125
+ record.message_queue.put_nowait(envelope.to_json())
126
+
127
+ def redrive_outbox(self) -> None:
128
+ """Schedule due outbox drains for live runs with senders."""
129
+ for record, loop in self._context.live_outbox_runs():
130
+ self._context.call_soon(self.drain_outbox, record, loop)
@@ -0,0 +1,188 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import AsyncIterator, Awaitable, Mapping
4
+ from pathlib import Path
5
+ from typing import Any, Protocol
6
+
7
+ from monoid_agent_kernel.core.agents import AgentRuntimeConfig
8
+ from monoid_agent_kernel.core.checkpoint import RunCheckpoint
9
+ from monoid_agent_kernel.core.lifecycle import SessionState
10
+ from monoid_agent_kernel.core.outbox import OutboxSender
11
+ from monoid_agent_kernel.core.result import AgentRunResult, Suspension
12
+
13
+
14
+ class TokenClaimsPort(Protocol):
15
+ tenant_id: str
16
+ user_id: str
17
+ run_id: str
18
+
19
+
20
+ class CancellationTokenPort(Protocol):
21
+ requested: bool
22
+
23
+
24
+ class MessageQueuePort(Protocol):
25
+ def qsize(self) -> int: ...
26
+
27
+ def put_nowait(self, item: Any) -> None: ...
28
+
29
+ def get(self) -> Awaitable[Any]: ...
30
+
31
+
32
+ def queued_message_snapshot(queue: MessageQueuePort) -> list[str | list[Any] | dict[str, Any]]:
33
+ raw_queue = getattr(queue, "_queue", ())
34
+ return [message for message in list(raw_queue) if isinstance(message, (str, list, dict))]
35
+
36
+
37
+ class LoopPort(Protocol):
38
+ def wait_for_pending_tasks(self, timeout_s: float) -> bool: ...
39
+
40
+ def has_pending_tasks(self) -> bool: ...
41
+
42
+ def emit_external_event(
43
+ self,
44
+ event_type: str,
45
+ *,
46
+ data: dict[str, Any] | None = None,
47
+ level: str = "info",
48
+ ) -> bool: ...
49
+
50
+ async def arun_until_suspended(self, user_input: Any | None = None) -> Suspension: ...
51
+
52
+ def fail_recoverable(self, error: str, *, error_code: str) -> None: ...
53
+
54
+ def await_user_input(self) -> None: ...
55
+
56
+ async def aclose(self) -> AgentRunResult: ...
57
+
58
+ def snapshot(self) -> RunCheckpoint | None: ...
59
+
60
+ def collect_checkpoint_blobs(self) -> Mapping[str, bytes]: ...
61
+
62
+ def interrupt_turn(self) -> None: ...
63
+
64
+ def pause_turn(self) -> None: ...
65
+
66
+ def revoke_capability(
67
+ self,
68
+ *,
69
+ capability: str | None = None,
70
+ lease_id: str | None = None,
71
+ before: float | None = None,
72
+ reason: str = "",
73
+ ) -> dict[str, Any]: ...
74
+
75
+ def report_task_result(
76
+ self,
77
+ task_id: str,
78
+ result: dict[str, Any],
79
+ *,
80
+ status: str,
81
+ persist_checkpoint: bool,
82
+ ) -> dict[str, Any]: ...
83
+
84
+ def create_task(self, kind: str, request: dict[str, Any]) -> str: ...
85
+
86
+ def restore(self, checkpoint: RunCheckpoint, *, blobs: Mapping[str, bytes]) -> None: ...
87
+
88
+
89
+ class RunStreamPort(Protocol):
90
+ suspension: Suspension | None
91
+
92
+ async def __aenter__(self) -> RunStreamPort: ...
93
+
94
+ async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> bool | None: ...
95
+
96
+ def __aiter__(self) -> AsyncIterator[Any]: ...
97
+
98
+
99
+ class RunExecutionLoopPort(LoopPort, Protocol):
100
+ async def aopen(self) -> None: ...
101
+
102
+ def astream(self, user_input: Any) -> RunStreamPort: ...
103
+
104
+
105
+ class LoopBuildPort(Protocol):
106
+ loop: RunExecutionLoopPort
107
+ outbox_sender: OutboxSender | None
108
+
109
+
110
+ class RunRecordPort(Protocol):
111
+ run_id: str
112
+ tenant_id: str
113
+ user_id: str
114
+ workspace_root: Path
115
+ run_dir: Path
116
+ state: SessionState
117
+ terminal: bool
118
+ created_at: float
119
+ started_at: float
120
+ finished_at: float
121
+ last_event_seq: int
122
+ last_event_type: str
123
+ error: str
124
+ error_code: str
125
+ result: AgentRunResult | None
126
+ last_final_output: Any
127
+ runtime_config: AgentRuntimeConfig | None
128
+ runtime_config_issuer: str
129
+ runtime_config_reason: str
130
+ runtime_config_committed_at: float
131
+
132
+
133
+ class MutableRunRecordPort(RunRecordPort, Protocol):
134
+ message_queue: MessageQueuePort
135
+ loop: LoopPort | None
136
+ cancellation_token: CancellationTokenPort
137
+ seen_inbox_ids: set[str]
138
+ outbox_sender: OutboxSender | None
139
+
140
+
141
+ class PreparedRunPort(Protocol):
142
+ run_id: str
143
+ record: MutableRunRecordPort
144
+ workspace_root: Path
145
+ run_token: str
146
+ llm_gateway_token: str
147
+ web_gateway_token: str
148
+
149
+
150
+ class RunRequestPort(Protocol):
151
+ tenant_id: str
152
+ user_id: str
153
+ workspace_root: Path
154
+ instruction: str
155
+ input_parts: tuple[Any, ...]
156
+ mode: str
157
+ workspace_backend: str
158
+ max_steps: int
159
+ max_tool_calls: int
160
+ max_bytes_read: int
161
+ max_duration_s: int | None
162
+ permission_policy: Any
163
+ runtime_config: AgentRuntimeConfig | None
164
+ multi_turn: bool
165
+ metadata: dict[str, Any]
166
+
167
+
168
+ class LeaseStorePort(Protocol):
169
+ def candidate_run_ids(self) -> list[str]: ...
170
+
171
+ def is_stale(self, run_id: str) -> bool: ...
172
+
173
+ def try_claim(self, run_id: str, worker_id: str, ttl_s: float) -> bool: ...
174
+
175
+ def release(self, run_id: str) -> None: ...
176
+
177
+
178
+ class DriveOpenSessionPort(Protocol):
179
+ def __call__(
180
+ self,
181
+ record: MutableRunRecordPort,
182
+ request: RunRequestPort,
183
+ loop: LoopPort,
184
+ suspension: Suspension,
185
+ *,
186
+ started: float,
187
+ turns: int,
188
+ ) -> Awaitable[AgentRunResult]: ...